鎖是個非常有用的工具,運用情境非常多,因為它使用起來非常簡單,而且易於理解。但同時它也會帶來一些困擾,那就是可能會引起死結,一旦產生死結,就會造成系統功能不可用。 死結的概念
那什麼是死結呢。所謂死結: 是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死結狀態或系統產生了死結,這些永遠在互相等待的進程稱為死結進程。 死結產生的必要條件
1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。如果此時還有其它進程請求資源,則要求者只能等待,直至佔有資源的進程用畢釋放。
2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。
3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。
4)環路等待條件:指在發生死結時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。 死結代碼執行個體
public class DeadLockDemo { private static String A = "A"; private static String B = "B"; public static void main(String[] args) { new DeadLockDemo().deadLock(); } /** * 死結 * @author fuyuwei * 2017年5月13日 下午9:27:32 */ private void deadLock() { Thread t1 = new Thread(new Runnable() { @SuppressWarnings("static-access") @Override public void run() { synchronized (A) { try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (B) { System.out.println("1"); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized (B) { synchronized (A) { System.out.println("2"); } } } }); t1.start(); t2.start(); }}
線程A睡眠2秒之後鎖定B同步列印1,但是這時候B已經被第二個線程鎖定,並且第二天線程又鎖定A列印2,就這樣A等待B但是握著B不放,B等待A但是握著A不放,就產生了死結。
當然這段代碼純粹是為了示範死結,在實際工作中基本上不會出現這種代碼。在實際工作中線程可能拿到一個資料庫鎖,釋放鎖的時候拋出了異常,沒釋放掉。
一旦出現死結,業務是可感知的,因為不能繼續提供服務了,那麼只能通過dump線程查看到底是哪個線程出現了問題,以下線程資訊告訴我們是DeadLockDemo類的第42行和第31行引起的死結。
"Thread-2" prio=5 tid=7fc0458d1000 nid=0x116c1c000 waiting for monitor entry [116c1b000java.lang.Thread.State: BLOCKED (on object monitor)at com.ifeve.book.forkjoin.DeadLockDemo$2.run(DeadLockDemo.java:42)- waiting to lock <7fb2f3ec0> (a java.lang.String)- locked <7fb2f3ef8> (a java.lang.String)at java.lang.Thread.run(Thread.java:695)"Thread-1" prio=5 tid=7fc0430f6800 nid=0x116b19000 waiting for monitor entry [116b18000java.lang.Thread.State: BLOCKED (on object monitor)at com.ifeve.book.forkjoin.DeadLockDemo$1.run(DeadLockDemo.java:31)- waiting to lock <7fb2f3ef8> (a java.lang.String)- locked <7fb2f3ec0> (a java.lang.String)at java.lang.Thread.run(Thread.j
避免死結的方法
1、避免一個線程同時擷取多個鎖。
2、避免一個線程在鎖內同時佔用多個資源,盡量保證每個鎖只佔用一個資源。
3、嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。
4、對於資料庫鎖,加鎖和解鎖必須在一個資料庫連接裡,否則會出現解鎖失敗的情況。 什麼是資源限制
資源限制是指在進行並發編程時,程式的執行速度受限於電腦硬體資源或軟體資源。例如,伺服器的頻寬只有2Mb/s,某個資源的下載速度是1Mb/s每秒,系統啟動10個線程下載資源,下載速度不會變成10Mb/s,所以在進行並發編程時,要考慮這些資源的限制。硬體資源限制有頻寬的上傳/下載速度、硬碟讀寫速度和CPU的處理速度。軟體資源限制有資料庫的串連數和socket串連數等。 資源限制引發的問題
在並發編程中,將代碼執行速度加快的原則是將代碼中串列執行的部分變成並發執行,但是如果將某段串列的代碼並發執行,因為受限於資源,仍然在串列執行,這時候程式不僅不會加快執行,反而會更慢,因為增加了環境切換和資源調度的時間。例如,之前看到一段程式使用多線程在辦公網並發地下載和處理資料時,導致CPU利用率達到100%,幾個小時都不能運行完成任務,後來修改成單線程,一個小時就執行完成了。 如何解決資源限制的問題
對於硬體資源限制,可以考慮使用叢集並存執行程式。既然單機的資源有限制,那麼就讓程式在多機上運行。比如使用ODPS、Hadoop或者自己搭建伺服器叢集,不同的機器處理不同的資料。可以通過“資料ID%機器數”,計算得到一個機器編號,然後由對應編號的機器處理這筆資料。對於軟體資源限制,可以考慮使用資源集區將資源複用。比如使用串連池將資料庫和Socket串連複用,或者在調用對方webservice介面擷取資料時,只建立一個串連。 在資源限制情況下進行並發編程
如何在資源限制的情況下,讓程式執行得更快呢。方法就是,根據不同的資源限制調整程式的並發度,比如下載檔案程式依賴於兩個資源——頻寬和硬碟讀寫速度。有資料庫操作時,涉及資料庫連接數,如果SQL語句執行非常快,而線程的數量比資料庫連接數大很多,則某些線程會被阻塞,等待資料庫連接。