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类下,换句话说,所有的类都有这两个方法。
另外,关于等待/通知,要记住的关键点是:
- 必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
- 与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该对象的通知。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到得到对象的notify()信号为止。
- 如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。
下面我用通知等待机制编写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 等待/通知 机制的几个特性:
- wait之后,该线程会立即释放同步Object的锁,并转入阻塞状态。
- 但是,一个线程调用了notify之后,并不会立刻放弃同步Object的锁,而是继续执行同步代码块内的代码。又因为都加了synchronized,线程串行执行,所以只有当这个线程执行完同步代码块内的代码,被成功唤醒的线程才能执行。
- 一个notify只能随机唤醒一个wait的线程(这个提一下,就不写代码证明了)
这问题分析完了,可上面的问题还没有解决啊。为了一篇博文的篇幅不要过长,这篇博文我就写到这里。下一篇博文直接介绍【消费者/生产者模式】,可以满足此处提出的需求,也是等待/通知机制的经典应用案例。
- Thread详解8:wait/notify机制
- Thread, wait(), notify(), sleep()
- Thread wait notify
- Thread wait notify用法
- thread wait notify
- Thread详解9:用wait/notify实现生产者/消费者模式
- [转]Wait-Notify机制
- Wait-Notify机制
- wait notify 锁机制
- Wait-Notify机制
- Wait-Notify机制
- wait(), notify(),sleep()详解
- java中wait/notify机制
- java多线程: wait/notify机制
- java中wait/notify机制
- java中wait/notify机制
- java中wait/notify机制
- java中wait/notify机制
- asp.net vb jquery ajax 取得后台数据
- running CORE—Common Open Research Emulator—in debian
- 网络模拟环境方案—CORE—NS—OMNeT++
- about image ranking --- papers
- BZOJ 1213: [HNOI2004]高精度开根
- Thread详解8:wait/notify机制
- Android 提升效率利器(持续更新)
- 安装numpy、nltk问题汇总
- Node.js - RESTful API - web service
- 算法 - 求二叉树的最大深度
- vim自定义语法高亮
- RESTful Web Services: A Tutorial
- opengl常见问题
- Mysql 性能统计