1 /* 2 * 文件名:NotifyDeadLockDemo.java 3 * 版权:Enmuser Technologies Co.,Ltd. Copyright 2016-2018 4 * 描述:<描述> 5 * 修改人:enmuser 6 * 修改时间:2018年2月24日 7 * 修改单号:<修改单号> 8 * 修改内容:<修改内容> 9 * 10 */ 11 package notify.deadLock; 12 13 /** 14 * <一句话功能描述> 15 * <功能详细描述> 16 * @author 朱洪昌 17 * @date 2018年2月24日 18 * @version 1.0 19 */ 20 class OutTurn 21 { 22 private boolean isSub = true; 23 private int count = 0; 24 25 public synchronized void sub() 26 { 27 try 28 { 29 while(!isSub) 30 { 31 this.wait(); 32 } 33 System.out.println("sub --- " + count); 34 isSub = false; 35 this.notify(); 36 } 37 catch (InterruptedException e) 38 { 39 e.printStackTrace(); 40 } 41 count++; 42 } 43 44 public synchronized void main() 45 { 46 try 47 { 48 while(isSub) 49 { 50 this.wait(); 51 } 52 System.out.println("main --- " + count); 53 isSub = true; 54 this.notify(); 55 } 56 catch (InterruptedException e) 57 { 58 e.printStackTrace(); 59 } 60 count++; 61 } 62 } 63 public class NotifyDeadLockDemo 64 { 65 public static void main(String[] args) 66 { 67 final OutTurn outTurn = new OutTurn(); 68 for (int i = 0; i < 100; i++) 69 { 70 new Thread(new Runnable() 71 { 72 73 @Override 74 public void run() 75 { 76 for (int j = 0; j < 5; j++) 77 { 78 outTurn.sub(); 79 } 80 81 } 82 }).start(); 83 84 new Thread(new Runnable() 85 { 86 87 @Override 88 public void run() 89 { 90 for (int j = 0; j < 5; j++) 91 { 92 outTurn.main(); 93 } 94 95 } 96 }).start(); 97 } 98 } 99 100 }
解释一下原因:
OutTurn类中的sub和main方法都是同步方法,所以多个调用sub和main方法的线程都会处于阻塞状态,等待一个正在运行的线程来唤醒它们。下面分别分析一下使用notify和notifyAll方法唤醒线程的不同之处:
上面的代码使用了notify方法进行唤醒,而notify方法只能唤醒一个线程,其它等待的线程仍然处于wait状态,假设调用sub方法的线程执行完后(即System. out .println("sub ---- " + count )执行完之后),所有的线程都处于等待状态,此时在sub方法中的线程执行了isSub=false语句后又执行了notify方法,这时如果唤醒的是一个sub方法的调度线程,那么while循环等于true,则此唤醒的线程也会处于等待状态,此时所有的线程都处于等待状态,那么也就没有了运行的线程来唤醒它们,这就发生了死锁。
如果使用notifyAll方法来唤醒所有正在等待该锁的线程,那么所有的线程都会处于运行前的准备状态(就是sub方法执行完后,唤醒了所有等待该锁的状态,注:不是wait状态),那么此时,即使再次唤醒一个sub方法调度线程,while循环等于true,唤醒的线程再次处于等待状态,那么还会有其它的线程可以获得锁,进入运行状态。
总结:notify方法很容易引起死锁,除非你根据自己的程序设计,确定不会发生死锁,notifyAll方法则是线程的安全唤醒方法。
背景知识
java中的锁池和等待池:http://blog.csdn.net/emailed/article/details/4689220
线程间协作:wait、notify、notifyAll:http://wiki.jikexueyuan.com/project/java-concurrency/collaboration-between-threads.html
java中的notify和notifyAll有什么区别?
作者:文龙
链接:https://www.zhihu.com/question/37601861/answer/145545371
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
链接:https://www.zhihu.com/question/37601861/answer/145545371
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
今天正好碰到这个问题,也疑惑了好久。看了一圈知乎上的答案,感觉没说到根上。所以自己又好好Google了一下,终于找到了让自己信服的解释。
先说两个概念:锁池和等待池
- 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
- 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
Reference:java中的锁池和等待池
然后再来说notify和notifyAll的区别
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
Reference:线程间协作:wait、notify、notifyAll
综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。
有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了