Java多執行緒7:死鎖
前言
死鎖單獨寫一篇文章是因為這是一個很嚴重的、必須要引起重視的問題。這不是誇大死鎖的風險,儘管鎖被持有的時間通常很短,但是作為商業產品的應用程式每天可能要執行數十億次獲取鎖->釋放鎖的操作,只要在這數十億次操作中只要有一次發生了錯誤,就可能導致程式中發生死鎖,並且即使通過壓力測試也不可能找出所有潛在的死鎖。
死鎖
一個經典的多執行緒問題。
當一個執行緒永遠地持有一個鎖,並且其他執行緒都嘗試去獲得這個鎖時,那麼它們將永遠被阻塞,這個我們都知道。如果執行緒A持有鎖L並且想獲得鎖M,執行緒B持有鎖M並且想獲得鎖L,那麼這兩個執行緒將永遠等待下去,這種情況就是最簡單的死鎖形式。
在資料庫系統的設計中考慮了監測死鎖以及從死鎖中恢復,資料庫如果監測到了一組事務發生了死鎖時,將選擇一個犧牲者並放棄這個事務。Java虛擬機器解決死鎖問題方面並沒有資料庫這麼強大,當一組Java執行緒發生死鎖時,這兩個執行緒就永遠不能再使用了,並且由於兩個執行緒分別持有了兩個鎖,那麼這兩段同步程式碼/程式碼塊也無法再運行了----除非終止並重啟應用。
死鎖是設計的BUG,問題比較隱晦。不過死鎖造成的影響很少會立即顯現出來,一個類可能發生死鎖,並不意味著每次都會發生死鎖,這只是表示有可能。當死鎖出現時,往往是在最糟糕的情況----高負載的情況下。
下面給出一個產生死鎖的簡單程式碼並且演示如何分析這是一個死鎖:
public class DeadLock { private final Object left = new Object(); private final Object right = new Object(); public void leftRight() throws Exception {synchronized (left) { Thread.sleep(2000); synchronized (right) { System.out.println("leftRight end!"); } } } public void rightLeft() throws Exception { synchronized (right) { Thread.sleep(2000); synchronized (left) { System.out.println("rightLeft end!"); } } } }
注意這裡一定要有"Thread.sleep(2000)"讓執行緒睡一覺,不然一個執行緒運行了,另一個執行緒還沒有執行,先執行的執行緒很有可能就已經連續獲得兩個鎖了。寫兩個執行緒分別呼叫它們:
public class Thread0 extends Thread { private DeadLock dl; public Thread0(DeadLock dl) { this.dl = dl; } public void run() { try { dl.leftRight(); } catch (Exception e) { e.printStackTrace(); } } }
public class Thread1 extends Thread { private DeadLock dl; public Thread1(DeadLock dl) { this.dl = dl; } public void run() { try { dl.rightLeft(); } catch (Exception e) { e.printStackTrace(); } } }
寫個main函式呼叫一下:
public static void main(String[] args) { DeadLock dl = new DeadLock(); Thread0 t0 = new Thread0(dl); Thread1 t1 = new Thread1(dl); t0.start(); t1.start(); while(true); }
至於結果,沒有結果,什麼語句都不會列印,因為死鎖了。下面演示一下如何定位死鎖問題:
1、jps獲得當前Java虛擬機器程序的pid
2、jstack列印堆疊。jstack列印內容的最後其實已經報告發現了一個死鎖,但因為我們是分析死鎖產生的原因,而不是直接得到這裡有一個死鎖的結論,所以別管它,就看前面的部分
先說明介紹一下每一部分的意思,以"Thread-1"為例:
(1)"Thread-1"表示執行緒名稱
(2)"prio=6"表示執行緒優先順序
(3)"tid=00000000497cec00"表示執行緒Id
(4)nid=0x219c
執行緒對應的本地執行緒Id,這個重點說明下。因為Java執行緒是依附於Java虛擬機器中的本地執行緒來執行的,實際上是本地執行緒在執行Java執行緒程式碼,只有本地執行緒才是真正的執行緒實體。Java程式碼中建立一個thread,虛擬機器在執行期就會建立一個對應的本地執行緒,而這個本地執行緒才是真正的執行緒實體。Linux環境下可以使用"top -H -p JVM程序Id"來檢視JVM程序下的本地執行緒(也被稱作LWP)資訊,注意這個本地執行緒是用十進位制表示的,nid是用16進製表示的,轉換一下就好了,0x219c對應的本地執行緒Id應該是8604。
(5)"[0x000000004a3bf000..0x000000004a3bf790]"表示執行緒佔用的記憶體地址
(6)"java.lang.Thread.State:BLOCKED"表示執行緒的狀態
解釋完了每一部分的意思,看下Thread-1處於BLOCKED狀態,Thread-0處於BLOCKED狀態。對這兩個執行緒分析一下:
(1)Thread-1獲得了鎖0x000000003416a4e8,在等待鎖0x000000003416a4d8
(2)Thread-0獲得了鎖0x000000003416a4d8,在等待鎖0x000000003416a4e8
由於兩個執行緒都在等待獲取對方持有的鎖,所以就這麼永久等待下去了。
3、注意一下使用Eclipse/MyEclipse,這段程式如果不點選控制檯上面的紅色方框去Terminate掉它,而是右鍵->Run As->1 Java Application的話,這個程序會一直存在的,這時候可以利用taskkill命令去終止沒有被Terminate的程序:
避免死鎖的方式
既然可能產生死鎖,那麼接下來,講一下如何避免死鎖。
1、讓程式每次至多隻能獲得一個鎖。當然,在多執行緒環境下,這種情況通常並不現實
2、設計時考慮清楚鎖的順序,儘量減少嵌在的加鎖互動數量
3、既然死鎖的產生是兩個執行緒無限等待對方持有的鎖,那麼只要等待時間有個上限不就好了。當然synchronized不具備這個功能,但是我們可以使用Lock類中的tryLock方法去嘗試獲取鎖,這個方法可以指定一個超時時限,在等待超過該時限之後變回返回一個失敗資訊
相關推薦
Java多執行緒7:死鎖
前言 死鎖單獨寫一篇文章是因為這是一個很嚴重的、必須要引起重視的問題。這不是誇大死鎖的風險,儘管鎖被持有的時間通常很短,但是作為商業產品的應用程式每天可能要執行數十億次獲取鎖->釋放鎖的操作,只要在這數十億次操作中只要有一次發生了錯誤,就可能導致程式中發生死鎖,並且即使通過壓力測試也不可能找出所有潛在
Java多執行緒--同步與死鎖:synchronized;等待與喚醒:wait、notify、notifyAll;生命週期
class Info{ // 定義資訊類 private String name = "李興華"; // 定義name屬性 private String content = "JAVA講師" ; // 定義content屬性 private boolean flag = false ; // 設
Java多線程7:死鎖
選擇 進程id 監測 while 發生 問題 println 導致 -h http://www.cnblogs.com/xrq730/p/4853713.html 前言 死鎖單獨寫一篇文章是因為這是一個很嚴重的、必須要引起重視的問題。這不是誇大死鎖的風險,盡管鎖被持有的時間
【JAVA多執行緒問題之死鎖】
一、死鎖是什麼? 舉個例子:兩個人一起吃飯,每個人都拿了一隻筷子,雙方都在等待對方將筷子讓給自己,結果兩個人都吃不了飯。這種情況和計算機中的死鎖情況很相似。 假設有兩個執行緒,互相等待對方釋放佔有的鎖,但是釋放鎖的條件又不可能形成,這時候死鎖就形成了。 還是買票的問題,有的時候時會發生死
Java多執行緒8:synchronized鎖重入
synchronized方法/塊的內部呼叫本類的其他synchronized方法/時,是可得到鎖的。 關鍵字synchronized擁有鎖重入的功能,也就是在使用synchronized時,當一個執行緒
多執行緒六:死鎖例子與排查
死鎖產生情況:雙方互相持有對方的鎖的情況死鎖示例程式碼:public class DealThread implements Runnable { public String username; p
Java多執行緒12:多執行緒的死鎖
Java執行緒死鎖是一個經典的多執行緒問題,因為不同的執行緒都在等待根本不可能被釋放的鎖,從而導致所有的任務都無法繼續完成。在多執行緒技術中,“死鎖”是必須避免的,因為這會造成執行緒的“假死”。 pac
java多執行緒3:關鍵字synchronized取得的鎖都是物件鎖,而不是把一段程式碼或者方法(函式)當作鎖
java多執行緒3:關鍵字synchronized取得的鎖都是物件鎖,而不是把一段程式碼或者方法(函式)當作鎖 a.當多個執行緒訪問同一個物件的時候,哪個執行緒先執行帶synchronized關鍵字的方法,哪個執行緒就該方法所屬物件的鎖Lock,那麼其他物件就智慧呈等待狀態。但是如果多個執行緒訪
多執行緒 共享資源 同步鎖 java Java多執行緒程式設計:Lock
Java多執行緒程式設計:Lock synchronized是java中的一個關鍵字,也就是說是Java語言內建的特性。那麼為什麼會出現Lock呢? 如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只
java多執行緒7.使用執行緒池
只有當任務都是同類型並且相互獨立時,執行緒池的效能才能達到最佳。如果將執行時間較長的與執行時間較短的任務混合在一起,那麼除非執行緒池很大,否則將可能造成擁塞,如果提交的任務依賴於其他任務,那麼除非執行緒池無線大,否則將可能造成死鎖。 例如飢餓死鎖:執行緒池中的任務需要無限等待一些必須由池中其他任務才能提供的
java多執行緒中顯式鎖的輪詢檢測策略
顯式鎖簡介 java5.0之前,在協調對共享物件的訪問時可以使用的機制只有synchronized和volatile,java5.0增加了一種新的機制:ReentrantLock。 鎖像synchronized同步塊一樣,是一種執行緒同步機制,與synchronized不同的是ReentrantLock提
gdb除錯多執行緒出現的死鎖
多執行緒的條件下,程式很容易出現死鎖,此時各個執行緒處於等待狀態,可以通過gdb除錯找到死鎖出現的地方。 例子: #include <stdio.h> #include <pthread.h> #include <uni
java多執行緒系列:通過對戰遊戲學習CyclicBarrier
CyclicBarrier是java.util.concurrent包下面的一個工具類,字面意思是可迴圈使用(Cyclic)的屏障(Barrier),通過它可以實現讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,所有被屏障攔截的執
java多執行緒--簡易使用同步鎖實現一對一交替列印
一、本例需要分析的地方不多,只需要使用一個同步鎖+一個計數器就能搞定,直接奉送原始碼吧: package com.example.liuxiaobing.statemodel.mutil_thr
Java多執行緒10:同步不具有繼承性
父類的同步操作子類是不可以繼承獲得的 package unit2; public class Demo8_tongbubujujichengxing { public static void m
Java多執行緒11:多執行緒同步操作
什麼情況下需要同步 1、當多執行緒併發,有多段程式碼同時執行時,有時希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作。這時就需要執行緒同步。 2、如果兩段程式碼是同步的,那麼同一段時間只能執行
Java多執行緒16:使用ReentrantLock類實現同步操作
在Java多執行緒中,可以使用synchronized關鍵字來實現執行緒之間同步互斥,但在JDK1.5中新增加了ReentrantLock類也能達到同步的效果,並且在擴充套件功能上也更加強大。 pack
Java多執行緒19:ReentrantReadWriteLock類
類ReentrantLock具有完全互斥排他的效果,即同一時間只有一個執行緒在執行ReentrantLock.lock()方法後面的任務。這樣做雖然保證了例項變數的執行緒安全性,但效率確是非常低下的。所
Java多執行緒22:
Java中執行緒的狀態分為6種。 1.初始(NEW):新建立了一個執行緒物件,但還沒有呼叫start()方法。 2.執行(RUNNABLE):Java執行緒中將就緒(ready)和執行中(running)兩種狀態籠統的稱為“執行”。執行緒物件建立後,其他執行緒(
Java多執行緒24:使執行緒具有有序性
正常的情況下,執行緒的執行時多個執行緒之間執行任務的時機是無序的。可以通過改造程式碼的方式使他們執行具有有序性。 程式碼如下: package unit7; public class Demo10_R