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則不會的例子也就好解釋了
