Thread详解8:wait/notify机制

来源:互联网 发布:网络兼职赚钱有哪些 编辑:程序博客网 时间:2024/04/30 11:06

我通过一个问题来引入这篇博文里要介绍的知识点。现在有这样一个需求,内存中有一个容器,容量为5,线程 AddOne 每隔一段时间就往这个容器里反复一个单位的货物,放11个就结束。线程 Supervisor 就是用来监控这个容器的,一旦容器满了,它就将这个容器清空。你的解决方案是什么?

1 利用while循环

AddOne.java

package medium2;import java.util.ArrayList;public class AddOne extends Thread {    private ArrayList<Integer> aList;    public AddOne(ArrayList<Integer> aList) {        super("AddOne");        this.aList = aList;    }    @Override    public void run() {        super.run();        // 往容器中放11个货物        for (int i = 0; i < 11; i++) {            aList.add(i);            System.out.printf(Thread.currentThread().getName() + " puts %d-th object into the container!\n", i + 1);            // 每隔1秒放一个            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 完成任务后退出        System.out.println(Thread.currentThread().getName() + " finished!");        System.exit(NORM_PRIORITY);    }}

Supervisor.java

package medium2;import java.util.ArrayList;public class Supervisor extends Thread {    private ArrayList<Integer> aList;    public Supervisor(ArrayList<Integer> aList) {        super("Supervisor");        this.aList = aList;    }    @Override    public void run() {        super.run();        while (true) {            // 一旦监测到容器已经满了就将其清空            if (aList.size() == 5) {                aList.clear();                System.out.println(Thread.currentThread().getName() + " has emptied the container");            } else {                System.out.println("The size is " + aList.size());            }            // 每隔300ms检查一次            try {                Thread.sleep(300);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] args) {        ArrayList<Integer> aList = new ArrayList<>();        AddOne thread1 = new AddOne(aList);        Supervisor thread2 = new Supervisor(aList);        thread1.start();        thread2.start();    }}

输出

The size is 0AddOne puts 1-th object into the container!The size is 1The size is 1The size is 1AddOne puts 2-th object into the container!The size is 2The size is 2The size is 2AddOne puts 3-th object into the container!The size is 3The size is 3The size is 3AddOne puts 4-th object into the container!The size is 4The size is 4The size is 4The size is 4AddOne puts 5-th object into the container!Supervisor has emptied the containerThe size is 0The size is 0AddOne puts 6-th object into the container!The size is 1The size is 1The size is 1AddOne puts 7-th object into the container!The size is 2The size is 2The size is 2The size is 2AddOne puts 8-th object into the container!The size is 3The size is 3The size is 3AddOne puts 9-th object into the container!The size is 4The size is 4The size is 4AddOne puts 10-th object into the container!Supervisor has emptied the containerThe size is 0The size is 0The size is 0AddOne puts 11-th object into the container!The size is 1The size is 1The size is 1AddOne finished!

上面这种做法的缺点在于:Supervisor按照既定的时间间隔去扫描容器状态,这样会消耗CPU资源;如果轮询的时间间隔很小,会更消耗CPU资源;如果轮询时间间隔过大,有可能错过了时机,造成了错误。比如容器已经满了,还往里面塞东西,结果溢出。

有没有一种更好的解决方案呢? 我在这里就不卖关子了,有的,常见的解决方案就是【等待/通知】机制。AddOne自己放货物到容器的时机就可以随便关注一下一下容器的状态,如果它发现容器已经满了,就通知Supervisor你把容器清空吧,我下次还要用。这种机制在Java中就对应与两个方法:wait和notify。下面我对它们的使用做一个入门级的介绍。



2 wait / notify

首先强调的一点是,这两个方法不在Thread下,而在java.lang.Object类下,换句话说,所有的类都有这两个方法。

这里写图片描述

另外,关于等待/通知,要记住的关键点是:

  1. 必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
  2. 与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该对象的通知。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到得到对象的notify()信号为止。
  3. 如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。

下面我用通知等待机制编写Section 1中提出的问题的解决方案。

AddOne2.java

package medium2;import java.util.ArrayList;public class AddOne2 extends Thread {    private ArrayList<Integer> aList;    public AddOne2(ArrayList<Integer> aList) {        super("AddOne2");        this.aList = aList;    }    @Override    public void run() {        super.run();        synchronized (aList) {            for(int i=0; i<11; i++){                aList.add(i);                System.out.printf(Thread.currentThread().getName() + " puts %d-th object into the container!\n", i + 1);                // 每隔1秒放一个                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                if(aList.size() == 5){                    aList.notify();                    System.out.println("AddOne2 已经通知 Supervisor 了");                }            }        }    }}

Supervisor2.java

package medium2;import java.util.ArrayList;public class Supervisor2 extends Thread {    private ArrayList<Integer> aList;    public Supervisor2(ArrayList<Integer> aList) {        super("Supervisor");        this.aList = aList;    }    @Override    public void run() {        super.run();        System.out.println("Supervisor has started running");        synchronized (aList) {            System.out.println("Supervisor has got the lock");            try {                // 等待通知                aList.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }            aList.clear();            System.out.println(Thread.currentThread().getName() + " has emptied the container");        }    }    public static void main(String[] args) {        ArrayList<Integer> aList = new ArrayList<>();        Supervisor2 thread2 = new Supervisor2(aList);        thread2.start();        // sleep是为了保证 Supervisor2 先wait,不然AddOne先notify了,wait就没有意义        try {            Thread.sleep(50);        } catch (InterruptedException e) {            e.printStackTrace();        }        AddOne2 thread1 = new AddOne2(aList);        thread1.start();    }}

输出

Supervisor has started runningSupervisor has got the lockAddOne2 puts 1-th object into the container!AddOne2 puts 2-th object into the container!AddOne2 puts 3-th object into the container!AddOne2 puts 4-th object into the container!AddOne2 puts 5-th object into the container!AddOne2 已经通知 Supervisor 了Supervisor has emptied the container

咦!怎么回事!?输出和预想的不一样!看来上面的代码有点问题,但是这个代码的意义比正确的代码更重要!因为好好分析一下,可以发现很多重要的性质。

  • 首先,最先运行的是Supervisor,它先获得了aList对象的锁,然后执行到wait,立刻就放弃了锁,这样AddOne才可以执行同步代码块内的代码。
  • AddOne添加5个货物后就通知了Supervisor,可是从输出我们看出,AddOne还在继续执行,直到AddOne的同步代码块内的代码执行完,Supervisor才执行,也才输出了 “Supervisor has emptied the container”,而这个时候容器早就爆了。
  • 另外一个值得注意的地方就是,在执行AddOne的for循环中我用了sleep,可是,即使在AddOne sleep的时间内,被唤醒的Supervisor还是没能得到锁,没能执行,为什么? 这也证明了sleep虽然让线程进入了阻塞状态,但是即使在sleep,该线程也不会释放所持有的锁,这一知识点很重要。

所以我再来补充强调Java 等待/通知 机制的几个特性:

  1. wait之后,该线程会立即释放同步Object的锁,并转入阻塞状态。
  2. 但是,一个线程调用了notify之后,并不会立刻放弃同步Object的锁,而是继续执行同步代码块内的代码。又因为都加了synchronized,线程串行执行,所以只有当这个线程执行完同步代码块内的代码,被成功唤醒的线程才能执行。
  3. 一个notify只能随机唤醒一个wait的线程(这个提一下,就不写代码证明了)

这问题分析完了,可上面的问题还没有解决啊。为了一篇博文的篇幅不要过长,这篇博文我就写到这里。下一篇博文直接介绍【消费者/生产者模式】,可以满足此处提出的需求,也是等待/通知机制的经典应用案例。

0 0
原创粉丝点击