系列文章传送门:java
Java多线程学习(二)synchronized关键字(1)程序员
java多线程学习(二)synchronized关键字(2) github
Java多线程学习(四)等待/通知(wait/notify)机制编程
系列文章将被优先更新于微信公众号<font color="red">“Java面试通关手册”</font>,欢迎广大Java程序员和爱好技术的人员关注。ide
本节思惟导图:
思惟导图源文件+思惟导图软件关注微信公众号:“Java面试通关手册” 回复关键字:“Java多线程” 免费领取。
当两个线程之间存在<font color="red">生产和消费者关系</font>,也就是说<font color="red">第一个线程(生产者)作相应的操做而后第二个线程(消费者)感知到了变化又进行相应的操做</font>。好比像下面的whie语句同样,假设这个value值就是第一个线程操做的结果,doSomething()是第二个线程要作的事,当知足条件value=desire后才执行doSomething()。
可是这里有个问题就是:第二个语句不停过经过轮询机制来检测判断条件是否成立。<font color="red">若是轮询时间的间隔过小会浪费CPU资源,轮询时间的间隔太大,就可能取不到本身想要的数据。因此这里就须要咱们今天讲到的等待/通知(wait/notify)机制来解决这两个矛盾</font>。
while(value=desire){ doSomething(); }
通俗来说:
等待/通知机制在咱们生活中比比皆是,一个形象的例子就是厨师和服务员之间就存在等待/通知机制。
用专业术语讲:
等待/通知机制,是指一个线程A调用了对象O的<font color="red">wait()方法</font>进入<font color="red">等待状态</font>,而另外一个线程B调用了对象O的<font color="red">notify()/notifyAll()方法</font>,线程A收到通知后退出<font color="red">等待队列</font>,进入可运行状态,进而执行后续操做。上诉两个线程经过对象O来完成交互,而对象上的wait()方法和notify()/notifyAll()方法的关系就如同开关信号同样,用来完成等待方和通知方之间的交互工做。
方法名称 | 描述 |
---|---|
notify() | 随机唤醒等待队列中等待同一共享资源的 “一个线程”,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个线程” |
notifyAll() | 使全部正在等待队列中等待同一共享资源的 “所有线程” 退出等待队列,进入可运行状态。此时,优先级最高的那个线程最早执行,但也有多是随机执行,这取决于JVM虚拟机的实现 |
wait() | 使调用该方法的线程释放共享资源锁,而后从运行状态退出,进入等待队列,直到被再次唤醒 |
wait(long) | 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,若是没有通知就超时返回 |
wait(long,int) | 对于超时时间更细力度的控制,能够达到纳秒 |
<font size="2">MyList.java</font>
public class MyList { private static List<String> list = new ArrayList<String>(); public static void add() { list.add("anyString"); } public static int size() { return list.size(); } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { if (MyList.size() != 5) { System.out.println("wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println("wait end " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private Object lock; public ThreadB(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { for (int i = 0; i < 10; i++) { MyList.add(); if (MyList.size() == 5) { lock.notify(); System.out.println("已发出通知!"); } System.out.println("添加了" + (i + 1) + "个元素!"); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(50); ThreadB b = new ThreadB(lock); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">运行结果:</font>
从运行结果:"wait end 1521967322359"最后输出能够看出,<font color="red">notify()执行后并不会当即释放锁。</font>下面咱们会补充介绍这个知识点。
<font color="red">synchronized关键字</font>能够将任何一个Object对象做为同步对象来看待,而<font color="red">Java为每一个Object都实现了等待/通知(wait/notify)机制的相关方法</font>,它们必须用在synchronized关键字同步的Object的临界区内。经过调用<font color="red">wait()方法</font>可使处于临界区内的线程进入<font color="red">等待状态</font>,同时<font color="red">释放被同步对象的锁</font>。而<font color="red">notify()方法</font>能够唤醒一个因调用wait操做而处于阻塞状态中的线程,使其进入就绪状态。被从新唤醒的线程会视图从新得到临界区的控制权也就是</font>锁</font>,并继续执行wait方法以后的代码。若是发出notify操做时没有处于阻塞状态中的线程,那么该命令会被忽略。
若是咱们这里不经过<font color="red">等待/通知(wait/notify)机制</font>实现,而是使用以下的<font color="red">while循环</font>实现的话,咱们上面也讲过会有很大的弊端。
while(MyList.size() == 5){ doSomething(); }
上面几章的学习中咱们已经掌握了与线程有关的大部分API,这些API能够改变线程对象的状态。以下图所示:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其余阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程从新转入可运行(runnable)状态。
备注:
能够用早起坐地铁来比喻这个过程:
还没起床:sleeping
起床收拾好了,随时能够坐地铁出发:Runnable
等地铁来:Waiting
地铁来了,但要排队上地铁:I/O阻塞
上了地铁,发现暂时没座位:synchronized阻塞
地铁上找到座位:Running
到达目的地:Dead
<font color="red">当方法wait()被执行后,锁自动被释放,但执行玩notify()方法后,锁不会自动释放。必须执行完otify()方法所在的synchronized代码块后才释放。</font>
下面咱们经过代码验证一下:
(完整代码:https://github.com/Snailclimb/threadDemo/tree/master/src/wait_notifyHoldLock)
<font size="2">带wait方法的synchronized代码块</font>
synchronized (lock) { System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName()); lock.wait(); System.out.println(" end wait() ThreadName=" + Thread.currentThread().getName()); }
<font size="2">带notify方法的synchronized代码块</font>
synchronized (lock) { System.out.println("begin notify() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); lock.notify(); Thread.sleep(5000); System.out.println(" end notify() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); }
若是有三个同一个对象实例的线程a,b,c,a线程执行带wait方法的synchronized代码块而后bb线程执行带notify方法的synchronized代码块紧接着c执行带notify方法的synchronized代码块。
<font size="2">运行效果以下:</font>
<font color="red">这也验证了咱们刚开始的结论:必须执行完notify()方法所在的synchronized代码块后才释放。</font>
<font color="red">当线程呈wait状态时,对线程对象调用interrupt方法会出现InterrupedException异常。</font>
<font size="2">Service.java</font>
public class Service { public void testMethod(Object lock) { try { synchronized (lock) { System.out.println("begin wait()"); lock.wait(); System.out.println(" end wait()"); } } catch (InterruptedException e) { e.printStackTrace(); System.out.println("出现异常了,由于呈wait状态的线程被interrupt了!"); } } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { Service service = new Service(); service.testMethod(lock); } }
<font size="2">Test.java</font>
public class Test { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(5000); a.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">运行结果:</font>
参考:
《Java多线程编程核心技术》
《Java并发编程的艺术》
若是你以为博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。
欢迎关注个人微信公众号:“Java面试通关手册”(分享各类Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取)。另外我建立了一个Java学习交流群(群号:174594747),欢迎你们加入一块儿学习,这里更有面试,学习视频等资源的分享。