线程之间的协作

引言

线程之间可以通过wait()notify()notifyAll()等方法,结合“生产者、消费者和队列”等设计模式来相互协作。线程之间协作的基础是任务之间的握手,其基础特性是互斥

wait()、notify()、notifyAll()方法介绍:
  • 以上三个方法都要获取对象监视器;
  1. wait():挂起线程,并放弃当前对象监视器;
  2. notify():唤醒一个等待‘notify()所在代码持有的对象监视器’的线程;
  3. notifyAll():唤醒所有等待线程,并让他们竞争对象监视器;
其他方法
  1. shutdown():
  2. shutdownNow():

一.wait()notifyAll()

1.1基本特性

当任务遇到运行需要但是自身无法改变的条件时,可以调用wait()来等待这个条件发生变化,而且只有在notify()、notifyAll()发生时才会检查条件是否发生了变化。

注意,只有在同步代码块中才能使用wait()方法(notify()方法同理),否则编译可以通过但是运行时会抛出IllegalMonitorStateException

sleep()、yield()不同的时:

  1. wait()期间锁是释放的,因此其他方法或代码块可以获得锁并执行;
  2. 可以通过notify()、notifyAll()或者指定时间的wait(XX,YY)方法来从wait()中恢复执行;
1.2源码示例

两个线程(打蜡-抛光)来给一辆车反复的打蜡-抛光。一共有4各类,汽车类、打蜡类、抛光类和驱动类:

class Car{
    private boolean waxOn=false;//刚开始没有打蜡

    public synchronized void waxed(){
        waxOn=true;//打上蜡
        notifyAll();//唤醒所有等待当前对象(this)监视器的线程;
    }

    public synchronized void buffed(){
        waxOn=false;//抛光,此时准备另一个车打蜡抛光;
        notifyAll();
    }
    public synchronized void waitForWaxing() throws InterruptedException {
        while(waxOn==false)//没打蜡,等待打蜡
            wait();
    }

    public synchronized void waitForBuffing() throws InterruptedException {
        while(waxOn==true)//打完蜡,等待抛光.
            wait();
    }
}

//打蜡类
class WaxOn implements Runnable{
    private Car car;

    public WaxOn(Car car) {
        this.car = car;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){//测试当前线程是否已经中断
                System.out.println("Wax On!");
                TimeUnit.MILLISECONDS.sleep(200);
                car.waxed();//打蜡
                car.waitForBuffing();//等待抛光
            }
        }catch (InterruptedException e){
            System.out.println("exetint via interrupt");
        }
        System.out.println("退出打蜡任务");
    }
}

class WaxOff implements Runnable{
    private Car car;

    public WaxOff(Car car) {
        this.car = car;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                car.waitForWaxing();//等待打蜡,如果当前car对象被另一个任务成功打蜡,则继续运行;
                System.out.println("wax off");
                TimeUnit.MILLISECONDS.sleep(200);
                car.buffed();//抛光
            }
        }catch (InterruptedException e){
            System.out.println("通过interrupt 退出");
        }
        System.out.println("抛光任务结束");
    }
}
public class WaxOmatic {
    public static void main(String[] args) throws InterruptedException {
        Car car=new Car();
        ExecutorService exec= Executors.newCachedThreadPool();
        exec.execute(new WaxOff(car));//抛光
        exec.execute(new WaxOn(car));//打蜡
        TimeUnit.SECONDS.sleep(5);
//        exec.shutdown();  无法退出;
        exec.shutdownNow();
    }
}

汽车类只有一个标识是否已经打蜡的变量,程序基本流程如下:

  1. 抛光线程获取car对象监视器,调用wait()方法放弃对象监视器;
  2. 打蜡线程获取car对象监视器,设置打蜡方法将变量设置为“已经打蜡”,然后唤醒等待car对象监视器的线程(notify方法),并在waitForBuffing()方法中放弃car对象监视器;
  3. 抛光线程被notify方法唤醒(因为他在等待car对象监视器),并开始抛光————然后将car打蜡状态置为FALSE,继续下一轮循环,car为FALSE状态则被打蜡线程中调用的“等待打蜡”方法感知,打蜡线程开始下一轮循环;
while(condition){ wait(); }+synchronized的正确使用方式
thread1:
synchronized(sharedMonitor){
    //setup condition for thread2
    sharedMonitor.notify();//等待sharedMonitor对象监视器的线程被唤醒;
}

thread2:
while(condition){
    //重要X:线程调度器可能再次切换线程;
    synchronized(sharedMonitor){
        sharedMonitor.wait();
    }
}

以上,如果thread2在“重要X”处被切换到thread1,然后thread1将thread2的while判定条件设置为false,并唤醒thread2,此时线程2将毫无意义的进入wait,并可能无限制的挂起来等待信号,从而产生死锁。正确的写法应该防在condition上产生竞争条件:

thread1:
synchronized(sharedMonitor){
    //setup condition for thread2
    sharedMonitor.notify();//等待sharedMonitor对象监视器的线程被唤醒;
}

thread2:
synchronized(sharedMonitor){
    while(condition){
        sharedMonitor.wait();
    }
}

二.生产者和消费者

生产者是厨师、消费者是服务员,对这个实例建模,有肉、厨师、服务员和驱动类(餐厅)四个类,代码如下:

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author 杜艮魁
 * @date 2018/1/31
 */


//Meal是Restaurant中未初始化的一个实例,并被其另外两个对象WaitPerson和Chef不断置null和实例化;
class Meal{
    private final int orderNum;

    public Meal(int orderNum) {
        this.orderNum = orderNum;
    }

    @Override
    public String toString() {
        return "Meal "+orderNum;
    }
}

//应当注意WaitPerson是Restaurant中的一个对象,对象创建方式
// WaitPerson waitPerson=new WaitPerson(this) 调用其构造参数如下,以此来使WaitPerson
//作为Restaurant的变量的同时能够操作Restaurant中的变量:即Meal引用状态和唤醒Chef线程
class WaitPerson implements Runnable{
    private Restaurant restaurant;

    public WaitPerson(Restaurant restaurant) {
        this.restaurant = restaurant;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                synchronized (this){
                    //对条件的判定放在锁里边,防止产生竞态条件
                    while(restaurant.meal==null)//
                        //加锁对象是this-WaitPerson对象:当厨房没有肉时,服务员等待;
                        wait();
                }
                System.out.println("waiter got "+restaurant.meal);

                //Meal不为null时Chef类中用this加锁的代码块被挂起。而以下代码将Meal置为null,因此需要唤醒Chef对象,修改meal引用;
                synchronized (restaurant.chef){
                    //服务员已经拿到肉,此时将肉实例设置为空,并唤醒等待上行锁的线程
                    restaurant.meal=null;
                    restaurant.chef.notifyAll();//fixme notifyAll()相当于this.notifyAll(),唤醒的是等待当前对象监视器的所有线程而非等待Restaurant对象中Chef对象监视器的线程;
                }
            }
        }catch (InterruptedException e){
            System.out.println("waiter interrupted");
        }
    }
}

//含义同WaitPerson
class Chef implements Runnable{
    private int count=0;
    Restaurant restaurant;

    public Chef(Restaurant restaurant) {
        this.restaurant = restaurant;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                synchronized (this){
                    while(restaurant.meal!=null)
                        wait();
                }
                if(++count==10){
                    System.out.println("out of food,closing");
                    restaurant.exec.shutdownNow();//fixme 注意关闭的方法仍然是调用Restaurant中的对象
                }
                System.out.println("order up!");//要上菜啦!!
                synchronized (restaurant.waitPerson){
                    restaurant.meal=new Meal(count);
                  //对应tag1处waitPerson发现"Meal"实例不为空时,在synchronized(this-waitPerson)代码块中调用wait——这句代码唤醒wait()的等待
                    restaurant.waitPerson.notifyAll();
                }
                TimeUnit.MILLISECONDS.sleep(100);
            }
        }catch (InterruptedException e){
            System.out.println("chef interrupted");
        }
    }
}

public class Restaurant {
    Meal meal;
    WaitPerson waitPerson=new WaitPerson(this);
    Chef chef=new Chef(this);

    ExecutorService exec= Executors.newCachedThreadPool();

    public Restaurant() {//创建对象时开始执行这两个线程
        exec.execute(chef);
        exec.execute(waitPerson);
    }

    public static void main(String[] args) {
        new Restaurant();
    }
}

注意代码中的两个TODO

  1. obj.notifyAll()和直接使用notifyAll()的区别是后者唤醒的是等待当前对象this监视器的线程;

三.生产者和消费者和队列

1.1吐司的制作与消费

设想模型:吐司制造、放黄油、加果酱以及被消费。一共有七个类:吐司类、吐司阻塞队列类,吐司生产类、放黄油类、放果酱类和消费类,驱动类。其中:

  • 吐司类含有id和当前状态——即是否摸了黄油和果酱等;吐司队列类继承自 LinkedBlockingQueue,也是一个重要的java类库;
  • 吐司生产类、放黄油类、放果酱类和消费类都是“任务类”负责通过队列操控生产;
  • 示例代码没有显示的同步,因为由LinkedBlockingQueue和系统设计隐式的处理了,根据LinkedBlockingQueue源码可以发现,使用队列消除了显式的使用wait()造成的类之间的耦合因为每个类都和自己相关的不lockingQueue通信;
  • 流程图如下:

    实例代码:

class ToastQueue extends LinkedBlockingQueue<Toast>{}


/**
 * @author 杜艮魁
 * @date 2018/1/31
 */

//id、状态,还有将他的状态设置为“添加黄油”、“添加果酱”
class Toast{
    public enum Status{DRY,BUTTERED,JAMMED}
    private Status status=Status.DRY;//默认状态为DRY
    private final int id;

    public Toast(int id) {
        this.id = id;
    }

    public void butter(){status=Status.BUTTERED;}
    public void jam(){status=Status.JAMMED; }

    public Status getStatus() {
        return status;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Toast "+id+": "+status;
    }
}


//吐司制造者:用其负责的吐司队列做构造参数
class Toaster implements Runnable{
    private ToastQueue toastQueue;
    private int count=0;
    private Random rand=new Random(47);

    public Toaster(ToastQueue toastQueue) {
        this.toastQueue = toastQueue;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                TimeUnit.MILLISECONDS.sleep(100+rand.nextInt(500));
                Toast t=new Toast(count++);
                System.out.println(t);
                toastQueue.put(t);//阻塞插入
            }
        }catch (InterruptedException e){
            System.out.println("Toaster interrupter");
        }
        System.out.println("Toast off");
    }
}

//给吐司抹黄油的类
class Butterer implements Runnable{
    private ToastQueue dryQueue,butteredQueue;

    public Butterer(ToastQueue dryQueue, ToastQueue butteredQueue) {
        this.dryQueue = dryQueue;
        this.butteredQueue = butteredQueue;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                //fixme 取出队头,如果队列为空则调用Lock的‘类wait()"方法
                Toast t=dryQueue.take();//阻塞获取
                t.butter();
                System.out.println(t);
                butteredQueue.put(t);
            }
        }catch(InterruptedException e){
            System.out.println("Butter interrupted");
        }

    }
}

class Jammer implements Runnable{
    private ToastQueue butteredQueue,finishedQueue;

    public Jammer(ToastQueue butteredQueue, ToastQueue finishedQueue) {
        this.butteredQueue = butteredQueue;
        this.finishedQueue = finishedQueue;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                Toast t=butteredQueue.take();
                t.jam();
                System.out.println(t);
                finishedQueue.put(t);
            }
        }catch (InterruptedException e){
            System.out.println("Jam interrupted");
        }
        System.out.println("jam off");
    }
}

class Eater implements Runnable{
    private ToastQueue finishedQueue;//即jammed后的吐司
    private int counter=0;

    public Eater(ToastQueue finishedQueue) {
        this.finishedQueue = finishedQueue;
    }

    @Override
    public void run() {
        try{
            while(!Thread.interrupted()){
                Toast t=finishedQueue.take();
                if(t.getId()!=counter++||t.getStatus()!=Toast.Status.JAMMED){//如果出队吐司序号不对或者状态不对,则关闭系统;
                    System.out.println("error: "+t);
                    System.exit(1);
                }else{
                    System.out.println("chomp! "+t);
                }
            }
        }catch (InterruptedException e){
            System.out.println("eater interrupted");
        }
        System.out.println("Eater off");
    }
}
public class ToastOMatic {
    public static void main(String[] args) throws InterruptedException {
        //fixme 无参构造器是默认存在的,导出类调用父类无参构造器—继而调用含参构造器,从而可以发现队列容量为Integer.MAX_VALUE
        ToastQueue dryQueue=new ToastQueue(),
                butteredQueue=new ToastQueue(),
                finishedQueue=new ToastQueue();

        ExecutorService exec= Executors.newCachedThreadPool();
        exec.execute(new Toaster(dryQueue));//启动一个“制作吐司”线程来初始化吐司的id和状态并放进dryQueue队列
        exec.execute(new Butterer(dryQueue,butteredQueue));//以上,同理
        exec.execute(new Jammer(butteredQueue,finishedQueue));
        exec.execute(new Eater(finishedQueue));

        TimeUnit.SECONDS.sleep(8);
        exec.shutdownNow();
    }
}

四.管道通信:任务间输入输出

管道中两个重要的类是PipedWriter和PipedReader,分别负责向管道中读写数据:

  1. PipedWriter:向管道中写数据;
  2. PipedReader:向管道中读数据;
    • pipedReader=new PipedReader(pipedWriter),每个piperReader读取的管道都要对应一个wirter,可以一对多;
/**
 * 线程间通过管道通信:
 *      1.PipedWriter:向管道中写数据;
 *      2.PipedReader:向管道中读数据;
 *          .pipedReader=new PipedReader(pipedWriter),每个piperReader读取的管道都要对应一个wirter
 */

class Sender implements Runnable{
    private PipedWriter out=new PipedWriter();

    public PipedWriter getOut() {
        return out;
    }

    @Override
    public void run() {
        try{
            while(true){
                for(char c='A';c<='z';c++){
                    out.write(c);//fixme 向管道中写数据
                    TimeUnit.MILLISECONDS.sleep(500);
                }
            }
        }catch (IOException e){
            System.out.println(e+" sender write exception");
        }catch (InterruptedException e){
            System.out.println(e+" sender sleep interrupted");
        }
    }
}

class Recerver implements Runnable{
    private PipedReader in;

    public Recerver(Sender sender) throws IOException {//注意参数是Sender类
        in=new PipedReader(sender.getOut());//fixme 用PipedWriter创建对应读取管道数据的类;
    }

    @Override
    public void run() {
        try{
            while(true){
                System.out.println("Read: "+(char)in.read()+",");
            }
        }catch (IOException e){
            System.out.println(e+" Receive read exception");
        }
    }
}
public class PipedIO {
    public static void main(String[] args) throws IOException, InterruptedException {
        Sender sender=new Sender();
        Recerver recerver=new Recerver(sender);
        ExecutorService executorService= Executors.newCachedThreadPool();

        executorService.execute(sender);
        executorService.execute(recerver);

        TimeUnit.SECONDS.sleep(4);
        executorService.shutdownNow();
    }
}

五.其他

Thread.join():等待此线程死亡。如果在一个线程中用另一个线程对象调用了join方法,那么当前线程会等待电泳join方法的线程结束才继续运行;

posted on 2018-04-21 13:53  coderDu  阅读(156)  评论(0编辑  收藏  举报