thread-communication

一、多线程通信

多个线程在处理同一资源,但是任务不同。

1、示例

input类:输入

resource类: name、sex

output类:输出

input和output作为两个线程,处理同一个资源resource。

class Resource{
    String name;
    String sex;
}

class Input implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            synchronized(r){//同步锁r与output保持一致
                if(x == 0){
                    r.name = "张三";
                    r.sex = "男";
                }else{
                    r.name = "李四";
                    r.sex = "女";
                }
            }
            x = (x+1)%2;
        }
    }
}

class Output implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){                    
            synchronized(r){
                System.out.println(r.name+"...."+r.sex);
            }
        }
    }
}

class ResourceDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

线程分析:

input拿到执行权后,输入张三男,output拿到执行权,输出张三、男,input再拿到执行输入李四女,还是input拿到执行权,输入张三男,覆盖了李四女,output拿到执行权还是输出张三男。因此,无法实现input输入一次,output就输出一次的需求。

2、多线程通信-等待唤醒机制

实现input输入一次,output就紧接着输出一次。

等待唤醒机制涉及的方法:

1、wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。

2、notify():唤醒线程池中一个线程(任意)。

3、notifyAll():唤醒线程池中的所有线程。

4、以上方法都必须定义在同步中。因为这些方法都是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。

5、r锁即为线程input和output的监视器。

6、为什么操作线程的方法wait、notify、notifyAll定义在了Object类中,而没有定义在Thread类中。因为这些方法是监视器的方法。监视器就是锁。锁可以是任意对象,任意对象都继承自Object类。

class Resource{
    String name;
    String sex;
    boolean flag = false;
}

class Input implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            synchronized(r){//同步锁r与output保持一致
                if(r.flag)
                    try{
                        r.wait()
                    }catch(InterruptedException e){
                        //...
                    }
                if(x == 0){
                    r.name = "张三";
                    r.sex = "男";
                }else{
                    r.name = "李四";
                    r.sex = "女";
                }
                r.flag = true;
                r.notify();
            }
            x = (x+1)%2;
        }
    }
}

class Output implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){                    
            synchronized(r){
                if(!r.flag)
                    try{
                        r.wait()
                    }catch(InterruptedException e){
                        //...
                    }
                    System.out.println(r.name+"...."+r.sex);
                    r.flag = false;
                    r.notify();
            }
        }
    }
}

class ResourceDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

线程分析:input和output分别取得执行权,并相互唤醒对方。

代码优化:不要直接访问Resource中的变量,提高安全性。

class Resource{
    private String name;
    private String sex;
    private boolean flag = false;
    public synchronized void set(String name, String sex){
        if(flag){
            try{
                    this.wait()
                }catch(InterruptedException e){
                    //...
                }
            this.name = name;
            this.sex = sex;
            flag = true;
            this.notify();//同步函数的锁是this
        }
    }
    public synchronized void out(){
        if(!flag){
            try{
                    this.wait()
                }catch(InterruptedException e){
                    //...
                }
        }
        System.out.println(name+"...."+sex);
        flag = false;
        this.notify();
    }
}

class Input implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            if(x == 0){
                r.set("张三","男");
            }else{
                r.set("张三","女");
            }
            x = (x+1)%2;
        }
    }
}

class Output implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){                    
            r.out();
        }
    }
}

class ResourceDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

3、多线程通信-多生产者多消费者问题

class Resource{
    private String name;
    private int count = 1;
    private boolean flag = false;
    public synchronized void set(String name){
        if(flag)
            try{
                this.wait()
            }catch(InterruptedException e){
                //...
            }
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
        flag = true;
        this.notify();//同步函数的锁是this
    }
    public synchronized void out(){
        if(!flag)
            try{
                this.wait()
            }catch(InterruptedException e){
                //...
            }
        System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
        flag = false;
        this.notify();
    }
}

class Producer implements Runnable{
    private Resource r;
    Producer(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){
            r.set("烤鸭");
        }
    }
}

class Consumer implements Runnable{
    private Resource r;
    Producer(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){
            r.out();
        }
    }
}

//只有一个生产者和一个消费者,安全
class ProducerConsumerDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Producer pro = new Producer();
        Consumer con = new Consumer();
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        t1.start();
        t2.start();
    }
}

//存在多个生产者和多个消费者,存在安全隐患
class ProducerConsumerDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Producer pro = new Producer();
        Consumer con = new Consumer();
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

线程分析:只有一个生产者,一个消费者时,没有问题。但是当有多个生产者和多个消费者是会存在安全隐患,甚至死锁。

隐患分析:

t0,t1,t2,t3都有执行资格。

线程t0拿到执行权,生产烤鸭1,flag为true。

t0再次拿到执行权被冻结。

t1拿到执行权也被冻结。此时,t0和t1都进入线程池。

t2拿到执行权,消费了烤鸭1,flag为false。唤醒线程池中被冻结的线程t0或t1。假设唤醒了t0。此时,t0,t2,t3有执行资格。

t2继续拿到执行权被冻结,进入线程池(t1,t2)。

t3拿到执行权也被冻结,进入线程池(t1,t2,t3)。

被唤醒的t0拿到执行权,没有判断if(flag),直接往下执行,生产烤鸭2,flag为true。唤醒线程池中的一个线程。

如果t1被唤醒,此时t0和t1有执行资格,线程池中还有t2和t3。

t0拿到执行权,被冻结进入线程池。

被唤醒的t1拿到执行权,还是没有判断if(flag),从被冻结的位置,直接往下执行,又生产了烤鸭3。

问题出现,烤鸭2没有被消费的情况下,生产了烤鸭3。

隐患解决方法:t1拿到执行权后,要判断flag为true,不要再生产烤鸭3。

因此要将if改为while

public synchronized void set(String name){
    while(flag)
        try{
            this.wait()
        }catch(InterruptedException e){
            //...
        }
    ...
}

public synchronized void out(){
    while(!flag)
        try{
            this.wait()
        }catch(InterruptedException e){
            //...
        }
    ...
}
死锁分析:

t0和t1被冻结进入线程池。

t2拿到执行权,消费烤鸭1,flag为false,唤醒t0。

t2和t3先后拿到执行权被冻结进入线程池。

t0拿到执行权,生产烤鸭,flag变为true,唤醒线程池中的任意一个线程。此时,线程池中有t1,t2,t3。

如果唤醒的是t1,此时有执行资格的为t0和t1。先后拿到执行权,由于flag为true,都被冻结进入线程池。此时,t0,t1,t2,t3都被冻结在线程池中。

没有线程被唤醒,没有线程有执行资格,没有线程能拿到执行权,死锁。

死锁解决:

t0拿到执行权,生产烤鸭,flag变为true,唤醒线程池中的线程时,全部唤醒。

public synchronized void set(String name){
    ...
    this.notifyAll();
}

public synchronized void out(String name){
    ...
    this.notifyAll();
}

4、多线程通信-多生产者多消费者问题-JDK1.5新特性-Lock

Lock接口是1.5版本对synchronized的替代。

同步代码块和同步函数对于锁的操作是隐式的。

jdk1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作。

Object obj = new Object();
void show(){
    synchronized(obj){
        //...
    }
}

Lock lock = new ReentrantLock();
void show(){
    try{
        lock.lock();//获取锁
        //...
    }catch(){
        //...
    }finally{
        lock.unlock();
    }
}

5、多线程通信-多生产者多消费者问题-JDK1.5新特性-Condition

Object obj = new Object();
void show(){
    synchronized(obj){
        //...
    }
}

同步代码块中的锁对象,只有一组操作线程的方法,就是继承自object的wait(),notify(),notifyAll()。

Lock lock = new ReentrantLock();
void show(){
    try{
        lock.lock();//获取锁
        //...
    }catch(){
        //...
    }finally{
        lock.unlock();
    }
}

jdk1.5以后将操作线程的方法封装为了对象即为condition接口。

Lock替代的是synchronized的方法和语句的使用,Condition替代了Object监视器(即锁)的方法(wait,notify,notifyAll)的使用。

通过Lock接口的newCondition方法,就可以使一个实现了Lock接口的锁对象生成多组监视器方法。

interface Condition{
    await();
    signal();
    signalAll();
}

Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
优化烤鸭示例代码
import java.util.concurrent.locks.*

class Resource{
    private ...
    ...
    //创建一个锁对象
    Lock lock = new ReentrantLock();
    //通过已有的锁获取该锁上的监视器对象
    Condition con = lock.newCondition();
    public void set(String name){
        lock.lock();
        try{
            while(flag)
                //try{this.wait();}catch(...){}
                try{con.await();}catch(...){}
                ...
                //notifyAll();
                con.signalAll();
        }finally{
            lock.unlock();
        }
    }
    public void out(){
        lock.lock();
        try{
            while(!flag)
                //try{this.wait();}catch(...){}
                try{con.await();}catch(...){}
                ...
                //notifyAll();
                con.signalAll();
        }finally{
            lock.unlock();
        }
    }
}
notifyAll解决方法缺点

唤醒了所有的线程,t1还有可能优于t2和t2拿到执行权,降低了效率。

应该想办法做到:set的线程t0和t1只唤醒out的t2和t3,而out的线程t2和t3只唤醒t0和t1。

lock和condition满足条件,四个线程共用一把锁lock,但是一把锁有两个监视器,一个监视器用来监视set,另一个监视器用来监视out。

优化烤鸭示例代码
Lock lock = new ReentrantLock();
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name){
    lock.lock();
    try{
        while(flag)
            //try{this.wait();}catch(...){}
            //try{con.await();}catch(...){}
            try{producer_con();}catch(...){}
            ...
            //notifyAll();
            //con.signalAll();
            //唤醒消费者的线程的一个线程即可
            consumer_con.signal();
    }finally{
        lock.unlock();
    }
}
public void out(){
    lock.lock();
    try{
        while(!flag)
            //try{this.wait();}catch(...){}
            //try{con.await();}catch(...){}
            try{consumer_con.await();}catch(...){}
            ...
            //notifyAll();
            //con.signalAll();
            producer_con.signal();
    }finally{
        lock.unlock();
    }
}
lock & condition

Lock接口:替代了同步代码块或者同步函数。将同步的隐式锁操作变成显式操作。同时更为灵活。可以给一个锁加上多组监视器。

lock():获取锁

unlock():释放锁

Condition接口:替代了Object中的wait,notify,notifyAll方法。将这些监视器的方法进行了封装,变成Condition监视器对象。可以任意锁进行组合。

await():相当于wait()方法
signal():相当于notify()方法
signalAll():相当于notifyAll()方法

6、wait和sleep的区别

1、wait可以指定时间,也可以不指定时间。sleep必须指定时间。

2、在同步中,对cpu的执行权和锁的处理不同。
wait:释放执行权,也释放锁。
sleep:释放执行权,不释放锁。

7、停止线程方式 - 定义标记

1、stop方法。已过时。

2、run方法结束

任务中都有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。

class StopThread implements Runnable{
    private boolean flag = true;
    public void run(){
        while(flag){
            System.out.println(Thread.currentThread().getName()+"...");
        }
    }
    public void setFlag(){
        flag = false;
    }
}

class StopThreadDemo{
    public static void main(String[] args){
        StopThread st = new StopThread();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        t1.start();
        t2.start();

        int num = 1;
        for(;;){
            if(++num == 50){
                st.setFlag(0;
                break;
            }
            System.out.println("main..."+num);
        }
        System.out.println("over");
    }
}

如果线程处于冻结状态,无法读取标记,可以用Thread类中的interrupt方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。

强制动作会发生中断异常,需要处理。

class StopThread implements Runnable{
    private boolean flag = true;
    public void run(){
        while(flag){
            try{
                wait();
            }catch(InterruptedException e){
                System.out.printlin(Thread.currentThread().getName()+"..."+e);
            }
            System.out.printlin(Thread.currentThread().getName()+"...+++");
        }
    }
    public void setFlag(){
        flag = false;
    }
}

class StopThreadDemo{
    public static void main(String[] args){
        StopThread st = new StopThread();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        t1.start();
        t2.start();

        int num = 1;
        for(;;){
            if(++num == 50){
                //st.setFlag(0;
                t1.interrupt();
                t2.interrupt();
                break;
            }
            System.out.println("main..."+num);
        }
        System.out.println("over");
    }
}

8、多线程 - 守护线程

setDaemon:后台线程,守护线程,用户线程。

运行时,跟前台线程一样,争夺cpu资源。

前台线程需要手动结束,后台线程当前台线程全部结束后自动结束。

setDaemon在线程开启之前调用。

t1.setDaemon(true);
t1.start();
t2.setDaemon(true);
t2.start();