關於死鎖,估計很多程式員都碰到過,並且有時候這種情況出現之後的問題也不是非常好排查,下麵整理的就是自己對死鎖的認識,以及通過一個簡單的例子來來接死鎖的發生,自己是做python開發的,但是對於死鎖的理解一直是一種模糊的概念,也是想過這次的整理更加清晰的認識這個概念。 用來理解的例子是一個簡單的生產者 ...
關於死鎖,估計很多程式員都碰到過,並且有時候這種情況出現之後的問題也不是非常好排查,下麵整理的就是自己對死鎖的認識,以及通過一個簡單的例子來來接死鎖的發生,自己是做python開發的,但是對於死鎖的理解一直是一種模糊的概念,也是想過這次的整理更加清晰的認識這個概念。
用來理解的例子是一個簡單的生產者和消費者模型,這裡是有一個生產者,有兩個消費者,並且註意代碼中使用notify方法的代碼行
package study_java.ex11; import java.util.LinkedList; import java.util.List; public class PCDemo1 { public static void main(String[] args){ Pool pool = new Pool(); Producter p1 = new Producter(pool); p1.setName("p1"); Consumer c1 = new Consumer(pool); Consumer c2 = new Consumer(pool); c1.setName("c1"); c2.setName("c2"); p1.start(); c1.start(); c2.start(); } } class Pool{ private List<Integer> list = new LinkedList<Integer>(); private int Max = 1; public void addLast(int n){ String name = Thread.currentThread().getName(); synchronized (this){ while (list.size() >= Max){ try{ System.out.println(name+".wait()"); this.wait(); } catch (Exception e){ e.printStackTrace(); } } System.out.println(name + "+" + n); list.add(new Integer(n)); System.out.println(name + ".notify()"); this.notify(); // 註意這裡是調用的是notify方法 } } public int remove(){ String name = Thread.currentThread().getName(); synchronized (this){ while (list.size() == 0){ try{ System.out.println(name + ".wait()"); this.wait(); } catch (Exception e){ e.printStackTrace(); } } System.out.println(name + "-" + 0); int no = list.remove(0); System.out.println(name + ".notify()"); this.notify(); // 註意這裡是調用的是notify方法 return no; } } } // 生產者 class Producter extends Thread{ private Pool pool; static int i = 1; public Producter(Pool pool){ this.pool = pool; } public void run(){ while (true){ pool.addLast(i++); System.out.println("生產者生產了"+i+"號"); } } } // 消費者 class Consumer extends Thread{ private Pool pool; public Consumer(Pool pool){ this.pool = pool; } public void run(){ while (true){ int no = pool.remove(); System.out.println("消費者消費了"+no+"號"); } } }
這段代碼的運行效果是日誌,在最後程式卡主不動了:
c1.wait() p1+1 p1.notify() c1-0 c1.notify() 消費者消費了1號 c1.wait() 生產者生產了2號 p1+2 p1.notify() c1-0 c1.notify() 消費者消費了2號 c1.wait() 生產者生產了3號 p1+3 p1.notify() c1-0 c1.notify() 消費者消費了3號 c1.wait() 生產者生產了4號 p1+4 p1.notify() c1-0 c1.notify() 消費者消費了4號 c1.wait() 生產者生產了5號 p1+5 p1.notify() c1-0 c1.notify() 消費者消費了5號 c1.wait() 生產者生產了6號 p1+6 p1.notify() 生產者生產了7號 c1-0 c1.notify() 消費者消費了6號 c1.wait() p1+7 p1.notify() 生產者生產了8號 p1.wait() c2-0 c2.notify() 消費者消費了7號 c2.wait() c1.wait() p1+8 p1.notify() 生產者生產了9號 p1.wait() c2-0 c2.notify() 消費者消費了8號 c2.wait() c1.wait()
對上面的出現卡主的情況進行分析,理解為啥會卡主:
從這次的執行效果可以看出第一次是c1搶到了執行權,但是這個時候pool是空
所以c1沒有可以消費的對象,被放入到了等待隊列
接著p1搶到了執行權,生產了1個,然後p1.notify(),這個時候等待隊列里只有c1,所以c1被喚醒,c1消費了1個,然後c1.notify(), 這個時候等待隊列也沒有等待的,這個時候有被c1搶到了執行權,但是pool里沒有可以消費的內容,所以c1.wait() 進入到等待隊列
這個時候p1搶到執行權,生產了1個,然後p1.notify(),這個時候等待隊列里只有c1,所以c1被喚醒,c1也搶到了執行權,消費了1個,然後c1.notify()
同樣這個時候等待隊列里沒有等待的,c1這次又搶到了執行權,但pool里沒有可以消費的內容,所以c1.wait(),進入到等待隊列
p1 又搶到了執行權,生產1個,然後p1.notify(),這個時候等待隊列里只有c1,所以c1被喚醒,c1也搶到了執行權,消費了1個,然後c1.notify()
同樣這個時候等待隊列里沒有等待的,c1這次又搶到了執行權,但pool里沒有可以消費的內容,所以c1.wait(),進入到等待隊列
.......這種情況重覆了幾次
但是運行到下麵這段的時候問題出現了:
p1+7 p1.notify() 生產者生產了8號 p1.wait() c2-0 c2.notify() 消費者消費了7號 c2.wait() c1.wait() p1+8 p1.notify() 生產者生產了9號 p1.wait() c2-0 c2.notify() 消費者消費了8號 c2.wait() c1.wait()
繼續進行分析,中間重覆的部分不做分析了,和前面的過程是一樣的
這個時候等待隊裡裡依然是c1 這個時候p1搶到了執行權,生產了1個,p1.notify() 這個時候等待隊列里只有c1,所以c1被喚醒,但是c1沒有搶過p1,p1自己又搶到了執行權,但是這個時候pool裡面已經有內容,所以p1沒有生產,p1.wait(),p1進入等待隊列
這個時候c2搶到了執行權,c2消費1個,c2.notify() 這個時候等待隊里是p1,p1被喚醒,但是這個時候c2搶到了執行權,但是pool沒有內容可以消費所以c2.wait() 進入等待隊列
接著c1搶到了執行權,同樣pool沒有可以消費的內容,c1.wait() 進入到等待隊列
p1這個時候搶到了執行權,p1生產了1個,接著p1.notify() 這個時候等待隊列里有c1和c2,但是只有一個會被喚醒,不管是哪個,結果沒搶過p1,p1再次拿到執行權,但是這個時候pool已經有內容,所以p1.wait() p1進入等待隊列
從下麵是c2執行,可以看出剛纔是c2被喚醒了,這個時候c2也拿到了執行權消費了1個。c2.notify() 等待隊列里這個時候有c1 和p1 但是這個時候c2 自己搶到了執行權,但是沒有可以消費的,c2.wait() c2 進入等待隊列
不巧的是剛纔搶到執行權的正好是c1,所以c1繼續wait,再次進入等待隊列
到這個時候p1 c1 c2 都進入等待隊列里,都在等待喚醒,也就出現了程勛最後卡住不動的情況
解決的方法有兩種:
第一種:
其實解決上面的方法也比較簡單,就是把調用notify的地方全部換成notifyAll方法
notify和notifyAll的區別是,當執行notifyAll的時候會喚醒所有等待的線程,從而避免之前的都在等待隊列等待的問題
第二種:
就是wait()的時候加上超時參數,不是像之前一直傻等,而是在超過既定的時間之後自己喚醒