Java併發編程,多線程死鎖與資源限制詳解

鎖是個很是有用的工具,運用場景很是多,由於它使用起來很是簡單,並且易於理解。但同時它也會帶來一些困擾,那就是可能會引發死鎖,一旦產生死鎖,就會形成系統功能不可用。java

死鎖的概念

那什麼是死鎖呢?所謂死鎖: 是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。web

死鎖產生的必要條件

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();
    }
    /**
     * 死鎖
     */
    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類的第39行和第28行引發的死鎖。編程

"Thread-2" prio=5 tid=7fc0458d1000 nid=0x116c1c000 waiting for monitor entry [116c1b000
java.lang.Thread.State: BLOCKED (on object monitor)
at com.ifeve.book.forkjoin.DeadLockDemo$2.run(DeadLockDemo.java:39)
- 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 [116b18000
java.lang.Thread.State: BLOCKED (on object monitor)
at com.ifeve.book.forkjoin.DeadLockDemo$1.run(DeadLockDemo.java:28)
- waiting to lock <7fb2f3ef8> (a java.lang.String)
- locked <7fb2f3ec0> (a java.lang.String)
at java.lang.Thread.run(Thread.j

避免死鎖的方法

一、避免一個線程同時獲取多個鎖。 
二、避免一個線程在鎖內同時佔用多個資源,儘可能保證每一個鎖只佔用一個資源。 
三、嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。 
四、對於數據庫鎖,加鎖和解鎖必須在一個數據庫鏈接裏,不然會出現解鎖失敗的狀況。服務器

什麼是資源限制

資源限制是指在進行併發編程時,程序的執行速度受限於計算機硬件資源或軟件資源。例如,服務器的帶寬只有2Mb/s,某個資源的下載速度是1Mb/s每秒,系統啓動10個線程下載資源,下載速度不會變成10Mb/s,因此在進行併發編程時,要考慮這些資源的限制。硬件資源限制有帶寬的上傳/下載速度、硬盤讀寫速度和CPU的處理速度。軟件資源限制有數據庫的鏈接數和socket鏈接數等。多線程

資源限制引起的問題

在併發編程中,將代碼執行速度加快的原則是將代碼中串行執行的部分變成併發執行,可是若是將某段串行的代碼併發執行,由於受限於資源,仍然在串行執行,這時候程序不只不會加快執行,反而會更慢,由於增長了上下文切換和資源調度的時間。例如,以前看到一段程序使用多線程在辦公網併發地下載和處理數據時,致使CPU利用率達到100%,幾個小時都不能運行完成任務,後來修改爲單線程,一個小時就執行完成了。併發

如何解決資源限制的問題

對於硬件資源限制,能夠考慮使用集羣並行執行程序。既然單機的資源有限制,那麼就讓程序在多機上運行。好比使用ODPS、Hadoop或者本身搭建服務器集羣,不一樣的機器處理不一樣的數據。能夠經過「數據ID%機器數」,計算獲得一個機器編號,而後由對應編號的機器處理這筆數據。對於軟件資源限制,能夠考慮使用資源池將資源複用。好比使用鏈接池將數據庫和Socket鏈接複用,或者在調用對方webservice接口獲取數據時,只創建一個鏈接。socket

在資源限制狀況下進行併發編程

如何在資源限制的狀況下,讓程序執行得更快呢?方法就是,根據不一樣的資源限制調整程序的併發度,好比下載文件程序依賴於兩個資源——帶寬和硬盤讀寫速度。有數據庫操做時,涉及數據庫鏈接數,若是SQL語句執行很是快,而線程的數量比數據庫鏈接數大不少,則某些線程會被阻塞,等待數據庫鏈接。ide

相關文章
相關標籤/搜索