1. 程式人生 > 其它 >JAVA篇:Java 多執行緒 (二) 執行緒鎖機制和死鎖

JAVA篇:Java 多執行緒 (二) 執行緒鎖機制和死鎖

2、執行緒鎖機制和死鎖

關鍵字:Java鎖分類名詞、執行緒死鎖、Lock、ReentrantLock、ReadWriteLock、Condition

說到鎖的話,總是會提到很多,其分類與理論部分應該會參考別人的描述,反正自己講也不會比別人好。

  • 公平鎖/非公平鎖

  • 可重入鎖

  • 獨享鎖/共享鎖

  • 互斥鎖/讀寫鎖

  • 樂觀鎖/悲觀鎖

  • 分段鎖

  • 偏向鎖/輕量級鎖/重量級鎖

  • 自旋鎖

還有一部分則是Java中鎖的實現與應用。

  • synchronized

  • Lock相關類

  • Condition相關類

2.1 鎖的分類名詞

前面所說的鎖的分類名詞,有的是指鎖的狀態、有的指鎖的特性、有的指鎖的設計。這部分主要是參考

JAVA鎖有哪些種類,以及區別(轉)

2.1.1 公平鎖/非公平鎖

公平鎖是指多個執行緒按照申請所的順序來獲取鎖。

非公平鎖是指多個執行緒獲取鎖的順序並不是按照申請鎖的順序,有可能是後申請的執行緒比先申請的執行緒有限獲取鎖。有可能,會造成優先順序反轉或者飢餓現象。

非公平鎖的優點在於吞吐量比公平鎖大。

在Java中,synchronized是一種非公平鎖。

ReentrantLock則可以通過建構函式指定該鎖是否公平鎖,預設是非公平鎖。ReentrantLock通過AQS來實現執行緒排程,實現公平鎖。

2.1.2 可重入鎖

可重入鎖又名遞迴鎖,是指在同一個執行緒在持有鎖的前提下,再遇到需要申請同一個鎖的情況時可自動獲取鎖。而非可重入鎖遇到這種情況會形成死鎖,也就是“我申請我已經持有的鎖,我不會釋放鎖也申請不到鎖,所以形成死鎖。”

Java中,

synchronized在JDK 1.6優化後,屬於可重入鎖。

ReentrantLock,即Re entrant Lock,可重入鎖。

synchronized void A(){
    System.out.println("A獲取鎖!");
    B();
}
synchronized void B(){
    System.out.println("B鎖重入成功!");
}

2.1.3 獨享鎖/共享鎖

獨享鎖是指該鎖一次只能被一個執行緒所持有,共享鎖是指該鎖可被多個執行緒所持有。

在Java中,

synchronized屬於獨享鎖。

ReentrantLock

也屬於獨享鎖。

而Lock的另一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。讀鎖的共享鎖可保證高效的併發讀,但是讀寫、寫讀、寫寫的過程是互斥的,防止髒讀、資料丟失。獨享鎖和共享鎖也是通過AQS實現的。

2.1.4 互斥鎖/讀寫鎖

上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。

互斥鎖在Java中的具體實現就是ReentraLock

讀寫鎖在Java中的具體實現就是ReadWriteLock

2.1.5 樂觀鎖/悲觀鎖

樂觀鎖與悲觀鎖不是指具體的什麼型別的鎖,而是指看待併發同步的角度。

悲觀鎖認為對同一個資料的併發操作,一定是會發生修改的,哪怕沒有修改,也會認為修改。因此對於同一個資料的併發操作,悲觀鎖採取加鎖的形式,悲觀地認為,不加鎖的併發操作一定會出現問題。

樂觀鎖則認為對於同一個資料的併發操作,是不會發生修改的。在更新資料的時候,會採用嘗試更新,不斷更新的方式更新資料,樂觀地認為,不加鎖的併發操作是沒有事情的。

從上面的描述我們可以看出,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的效能提升。

悲觀鎖在Java中的使用就是利用各種鎖。

樂觀鎖在Java中的使用就是無鎖程式設計,常常採用的是CAS演算法,典型的例子就是原子類,通過CAS自旋實現原子操作的更新。

2.1.6 分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。

我們以ConcurrentHashMap來說一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱為segment,它類似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry陣列,陣列中的每個元素又是一個連結串列;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。

當需要put元素的時候,並不是對整個hashmap進行加鎖,而是先通過hashcode來知道它要放在哪一個分段中,然後對這個分段進行加鎖,所以當多執行緒put的時候,只要不死放在一個酚酸中,就實現了真正的並行插入。

但是在統計size的時候,即獲取hashmap全域性資訊的時候,就需要獲取所有的分段鎖才能統計。

分段鎖的設計目的就是細化鎖的粒度,當操作不需要更新整個陣列的時候,就針對資料的一項進行加鎖操作。

2.1.7 偏向鎖/輕量級鎖/重量級鎖

這三種所是指鎖的狀態,並且是針對synchronized。在 java 6通過引入鎖的升級機制來實現高效synchronized。這三種鎖的狀態是通過物件監視器在物件頭中的欄位來表明的。

偏向鎖是指一段同步程式碼一直被一個執行緒所訪問,那麼該執行緒會自動獲取鎖,降低獲取鎖的代價。

輕量級鎖是指當鎖是偏向鎖的時候,被另一個執行緒所訪問,偏向鎖就會升級為輕量級鎖,其他執行緒會通過自旋的形式嘗試獲取鎖,不會阻塞,提高效能。

重量級鎖是指當鎖為輕量級鎖的時候,另一個執行緒雖然是自旋,但自旋不會持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的執行緒進入阻塞,效能降低。

2.1.8 自旋鎖

在Java中。自旋鎖是指嘗試獲取鎖的執行緒不會立即阻塞,而是採用迴圈的方式去嘗試獲取鎖,這樣的好處是減少執行緒上下文切換的消耗,缺點是迴圈會消耗CPU。

2.2 執行緒死鎖

死鎖是一個經典的多執行緒問題。避免死鎖重要嗎?一旦一組Java執行緒發生死鎖,那麼這組執行緒及鎖涉及其已經持有鎖的資源區將不再可用--除非重啟應用。

死鎖是設計上的bug,它並不一定發生,但它有可能發生,而且發生的情況一般出現在極端的高負載的情況下。

那麼有什麼辦法為了避免死鎖?

  1. 讓程式每次至多隻能獲得一個鎖。但這個在多執行緒環境下通常不現實。

  2. 設計時考慮清楚鎖的順序,儘量減少潛在的加鎖互動數量

  3. 避免使用synchronized,為執行緒等待設定等待上限,避免無限等待。

2.3 Lock和Condition

JVM提供了synchronized關鍵字來實現對變數的同步訪問以及用wait和notify來實現執行緒間通訊。在jdk1.5以後,Java提供了Lock類來提供更加豐富的鎖功能,並且還提供了Condition來實現執行緒間通訊。

Lock和Condition都屬java.util.concurrent.locks下的介面,如下圖所示。其中Lock的實現類包含ReentrantLockReadWriteLock,而Condition物件是通過lock物件建立的。

其中的AbstractOwnableSynchronizer(AQS),即抽象的佇列式的同步器,定義了一套多執行緒訪問共享資源的同步器框架,許多同步類實現都依賴於它,如ReentrantLock/Semaphore/CountDownLatch...

在這裡對AQS暫不做展開。Java併發之AQS詳解

2.4 ReentrantLock

之前有說過ReentrantLock是一個可重入互斥的鎖,在其建構函式中可以設定其是否是公平鎖。

/* 建立一個 ReentrantLock的例項。*/
ReentrantLock()
/* 根據給定的公平政策建立一個 ReentrantLock的例項。。*/
ReentrantLock(boolean fair)

相比於synchronized,ReentrantLock提供了更加豐富靈活的功能。

  1. void lock():獲得鎖。

  2. void unlock():嘗試釋放此鎖。

  3. boolean tryLock():只有在呼叫時它不被另一個執行緒佔用才能獲取鎖。

  4. boolean tryLock(long timeout, TimeUnit unit):有限制超時的請求鎖,如果執行緒沒有被中斷且沒有超時則可以獲得鎖。

  5. void lockInterruptibly() : 獲取鎖,除非被中斷。

  6. Condition newCondition():返回一個Condition例項。

  7. boolean isFair():該鎖是否為公平鎖。

  8. boolean isHeldByCurrentThread():查詢此鎖是否由當前執行緒持有。

  9. boolean isLocked():查詢此鎖是否由任何執行緒持有。

  10. int getHoldCount():查詢當前執行緒對此鎖的阻塞數量。

  11. protected Thread getOwner():返回當前擁有此鎖的執行緒,如果不擁有,則返回 null 。

  12. protected Collection<Thread> getQueuedThreads():返回可能正在等待獲取此鎖的執行緒的集合。

  13. int getQueueLength():返回等待獲取此鎖的執行緒數的估計。

  14. boolean hasQueuedThread(Thread thread):查詢給定執行緒是否等待獲取此鎖。

  15. boolean hasQueuedThreads():查詢是否有執行緒正在等待獲取此鎖。

  16. protected Collection<Thread> getWaitingThreads(Condition condition):返回正在等待(wait)給定Condition的執行緒集合,傳入的Condition必須與鎖相關聯。

  17. int getWaitQueueLength(Condition condition):返回正在等待(wait)給定Condition的執行緒數量估計,傳入的Condition必須與鎖相關聯。

  18. boolean hasWaiters(Condition condition):查詢是否有任何執行緒等待(wait)給定Condition,傳入的Condition必須與鎖相關聯。

2.4.1 請求鎖釋放鎖

ReentrantLock除了提供與synchronized鎖功能相似的無限阻塞的鎖請求lock()/unlock(),還提供了可限制阻塞超時的tryLock()/tryLock(timeout),可以更加靈活地處理鎖相關的操作,防止執行緒死鎖。

同時還可以檢視當前鎖持有、阻塞的情況。

測試程式碼如下:

    /* 測試ReentrantLock鎖請求與釋放 */
    public void test1(){
        /* 建立鎖例項ReentrantLock */
        Lock rlock = new ReentrantLock();
       // System.out.println(rlock.toString());
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
​
        /* 執行緒1從頭到尾持有鎖 */
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
​
                rlock.lock();
                System.out.println(df.format(new Date())+" part11:子執行緒1先持有鎖rlock,然後休眠5S");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(df.format(new Date())+" part12:子執行緒1釋放鎖rlock");
                rlock.unlock();
​
            }
        };
​
        Thread t1 = new Thread(r1);
        t1.start();
​
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        /* 執行緒2使用lock申請鎖 */
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                System.out.println(df.format(new Date())+" part21:子執行緒2使用lock()申請鎖rlock,會阻塞");
                rlock.lock();
                System.out.println(df.format(new Date())+" part22:子執行緒2獲得了鎖rlock,然後釋放鎖");
                rlock.unlock();
            }
        };
​
        Thread t2 = new Thread(r2);
        t2.start();
​
        /* 執行緒3使用trylock\trylock(timeout)申請鎖 */
        Runnable r3 = new Runnable() {
            @Override
            public void run() {
                System.out.println(df.format(new Date())+" part31:子執行緒3使用trylock()申請鎖rlock");
                if(rlock.tryLock()){
                    System.out.println(df.format(new Date())+" part32:子執行緒3獲得了鎖rlock,然後釋放鎖");
                    rlock.unlock();
                }else{
                    System.out.println(df.format(new Date())+" part33:子執行緒3沒有獲得鎖rlock,使用帶1S超時的trylock申請鎖");
​
                    try {
                        if(rlock.tryLock(1000, TimeUnit.MILLISECONDS)){
                            System.out.println(df.format(new Date())+" part34:子執行緒3獲得了鎖rlock,然後釋放鎖");
                            rlock.unlock();
                        }else{
                            System.out.println(df.format(new Date())+" part35:子執行緒3沒有獲得鎖rlock,超時退出");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
​
        Thread t3 = new Thread(r3);
        t3.start();
​
        /* 主執行緒檢視當前鎖與執行緒的情況 */
        System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否為公平鎖:"+((ReentrantLock) rlock).isFair());
        System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否被任意執行緒持有:"+((ReentrantLock) rlock).isLocked());
        System.out.println(df.format(new Date())+" 主執行緒檢視-查詢是否有執行緒等待獲取rlock:"+((ReentrantLock) rlock).hasQueuedThreads());
        System.out.println(df.format(new Date())+" 主執行緒檢視-查詢子執行緒2是否等待獲取鎖rlock:"+((ReentrantLock) rlock).hasQueuedThread(t2));
        System.out.println(df.format(new Date())+" 主執行緒檢視-查詢等待獲取鎖rlock的執行緒數估計:"+((ReentrantLock) rlock).getQueueLength());
​
       /* 主執行緒嘗試獲取鎖 */
        rlock.lock();
        System.out.println(df.format(new Date())+" part01:主執行緒獲得鎖rlock");
        System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否被主執行緒持有:"+((ReentrantLock) rlock).isHeldByCurrentThread());
        System.out.println(df.format(new Date())+" 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:"+((ReentrantLock) rlock).getHoldCount());
        rlock.lock();
        System.out.println(df.format(new Date())+" part02:主執行緒重入鎖rlock");
        System.out.println(df.format(new Date())+" 主執行緒檢視-rlock是否被主執行緒持有:"+((ReentrantLock) rlock).isHeldByCurrentThread());
        System.out.println(df.format(new Date())+" 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:"+((ReentrantLock) rlock).getHoldCount());
        rlock.unlock();
        rlock.unlock();
​
        System.out.println(df.format(new Date())+"測試結束。");
​
​
    }

輸出結果如下:

2021-10-11 09:54:03:122 part11:子執行緒1先持有鎖rlock,然後休眠5S
2021-10-11 09:54:03:124 part21:子執行緒2使用lock()申請鎖rlock,會阻塞
2021-10-11 09:54:03:124 主執行緒檢視-rlock是否為公平鎖:false
2021-10-11 09:54:03:125 主執行緒檢視-rlock是否被任意執行緒持有:true
2021-10-11 09:54:03:125 part31:子執行緒3使用trylock()申請鎖rlock
2021-10-11 09:54:03:125 主執行緒檢視-查詢是否有執行緒等待獲取rlock:true
2021-10-11 09:54:03:125 part33:子執行緒3沒有獲得鎖rlock,使用帶1S超時的trylock申請鎖
2021-10-11 09:54:03:125 主執行緒檢視-查詢子執行緒2是否等待獲取鎖rlock:true
2021-10-11 09:54:03:125 主執行緒檢視-查詢等待獲取鎖rlock的執行緒數估計:1 #這裡為什麼會是1?
2021-10-11 09:54:04:132 part35:子執行緒3沒有獲得鎖rlock,超時退出
2021-10-11 09:54:08:123 part12:子執行緒1釋放鎖rlock
2021-10-11 09:54:08:123 part22:子執行緒2獲得了鎖rlock,然後釋放鎖
2021-10-11 09:54:08:124 part01:主執行緒獲得鎖rlock
2021-10-11 09:54:08:124 主執行緒檢視-rlock是否被主執行緒持有:true
2021-10-11 09:54:08:124 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:1
2021-10-11 09:54:08:125 part02:主執行緒重入鎖rlock
2021-10-11 09:54:08:125 主執行緒檢視-rlock是否被主執行緒持有:true
2021-10-11 09:54:08:125 主執行緒檢視-查詢當前執行緒對rlock的阻塞數量:2
2021-10-11 09:54:08:125測試結束。

對結果有疑惑的地方在於getQueueLength()為什麼會線上程2、3都等待著鎖的情況下,結果是1?後面經驗證,getQueueLength()僅統計lock()後阻塞的執行緒,trylock(timeout)的等待應該底層有所不同。

2.4.2 中斷與鎖請求

lockInterruptibly()與lock()都是鎖請求方法,不過lockInterruptibly()提供了響應中斷請求以及處理中斷請求的功能,使得在進行鎖請求時,執行緒可以被中斷。

測試程式碼如下所示:

    /* 測試中斷與鎖請求 */
    public void test3() {
        /* 建立鎖例項ReentrantLock */
        Lock rlock = new ReentrantLock();
​
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        /* 子執行緒1使用lock等待 */
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                    System.out.println(df.format(new Date()) + " part11:子執行緒1使用lock請求鎖");
                    rlock.lock();
                    System.out.println(df.format(new Date()) + " part12:子執行緒1獲得鎖");
                    rlock.unlock();
                    System.out.println(df.format(new Date()) + " part13:子執行緒1釋放鎖");
                } catch (InterruptedException e) {
                    System.out.println(df.format(new Date()) + " part19:子執行緒1被中斷!");
                }
​
            }
        };
​
        Thread t1 = new Thread(r1);
        t1.start();
​
        /* 子執行緒2使用lockInterruptibly() */
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                    System.out.println(df.format(new Date()) + " part21:子執行緒2使用lockInterruptibly請求鎖");
                    rlock.lockInterruptibly();
                    System.out.println(df.format(new Date()) + " part22:子執行緒2獲得鎖");
                    rlock.unlock();
                    System.out.println(df.format(new Date()) + " part23:子執行緒2釋放鎖");
​
                } catch (InterruptedException e) {
                    System.out.println(df.format(new Date()) + " part29:子執行緒2被中斷!");
                }
​
            }
        };
​
        Thread t2 = new Thread(r2);
        t2.start();
​
        /* 主執行緒先持有鎖使得其他子執行緒等待,然後在合適的時間中斷兩個執行緒 */
        System.out.println(" 主執行緒先持有鎖使得其他子執行緒等待,然後在合適的時間中斷兩個執行緒 ");
​
        rlock.lock();
        System.out.println(df.format(new Date()) + " part01:主執行緒獲得鎖後休眠,防止在sleep的時候中斷");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println(df.format(new Date()) + " part02:主執行緒中斷子執行緒1");
        t1.interrupt();
        System.out.println(df.format(new Date()) + " part03:主執行緒中斷子執行緒2");
        t2.interrupt();
        System.out.println(df.format(new Date()) + " part04:主執行緒釋放鎖");
        rlock.unlock();
        System.out.println(df.format(new Date()) + " 測試結束");
​
​
    }

結果如下,可以看出來子執行緒2成功被中斷了,而子執行緒1中斷之後並無反應:

 主執行緒先持有鎖使得其他子執行緒等待,然後在合適的時間中斷兩個執行緒 
2021-10-11 10:22:12:435 part01:主執行緒獲得鎖後休眠,防止在sleep的時候中斷
2021-10-11 10:22:12:539 part21:子執行緒2使用lockInterruptibly請求鎖
2021-10-11 10:22:12:539 part11:子執行緒1使用lock請求鎖
2021-10-11 10:22:12:647 part02:主執行緒中斷子執行緒1
2021-10-11 10:22:12:647 part03:主執行緒中斷子執行緒2
2021-10-11 10:22:12:647 part04:主執行緒釋放鎖
2021-10-11 10:22:12:647 part29:子執行緒2被中斷!
2021-10-11 10:22:12:647 測試結束
2021-10-11 10:22:12:647 part12:子執行緒1獲得鎖
2021-10-11 10:22:12:647 part13:子執行緒1釋放鎖

2.5 ReadWriteLock

鎖會導致執行緒阻塞,大大降低了多執行緒處理資料的效率。前面有提到過對於鎖的優化,可以將讀寫分離,當只需要進行併發讀的時候,並不需要進行加鎖操作。

ReadWriteLock是一個介面,它提供了一個讀鎖和一個寫鎖,其實現在 ReentrantReadWriteLock及其內部靜態類 ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock。

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}

2.5.1 ReentrantReadWriteLock

ReentrantReadWriteLock.ReadLockReentrantReadWriteLock.ReentrantReadWriteLock.WriteLock都提供了基本的鎖操作,lock()、trylock()、tryLock(long timeout, TimeUnit unit)、lockInterruptibly()、unlock()、newCondition()。其中WriteLock還包含方法isHeldByCurrentThread()和getHoldCount()。

讀鎖和寫鎖的例項物件分別由ReentrantReadWriteLock的兩個例項方法readLock()和writeLock()返回。兩個鎖成對,在申請鎖的時候有一定的關聯。

讀鎖ReadLock申請鎖、獲取鎖的前提是,同一ReentrantReadWriteLock的寫鎖不被其他執行緒佔有。

寫鎖WriteLock申請鎖、獲取鎖的前提是,寫鎖及同一ReentrantReadWriteLock的讀鎖不被其他執行緒佔有。

2.5.2 讀寫鎖測試程式碼

讀寫鎖主要是為了防止多執行緒操作檔案是發生衝突,導致檔案結果與預期不符,同時讀鎖又保證了併發讀取資料時多執行緒效率問題。這裡測試程式碼主要看鎖的情況,有關於多執行緒讀寫後面再拓展討論。

測試程式碼如下,執行過程中可以看到偶爾會有多個執行緒同時進行讀操作:

    /* 測試讀寫鎖 */
    public  void test4(){
        /* 建立讀寫鎖 */
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock  readlock = lock.readLock();
        ReentrantReadWriteLock.WriteLock  writelock = lock.writeLock();
        Random random = new Random();
​
        class RThread extends Thread{
            private  int tag;
​
            public RThread(int tag, ReentrantReadWriteLock lock){
                this.tag = tag;
​
            }
            @Override
            public void run(){
                int n = 5;
                while (n>0){
                    n--;
                    /*隨機休眠
                    try {
                        Thread.sleep(random.nextInt(300));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
​
                    readlock.lock();
                    System.out.println(String.format("讀取執行緒%d:當前讀鎖數量:%d,是否寫鎖定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked()));
​
                    System.out.println(String.format("讀取執行緒%d:進行讀取......剩餘次數:%d",this.tag,n));
                    readlock.unlock();
​
                }
​
            }
        }
​
        class WThread extends Thread{
            private  int tag;
​
​
            public WThread(int tag,ReentrantReadWriteLock lock){
                this.tag = tag;
            }
            @Override
            public void run(){
                int n = 5;
                while (n>0){
                    n--;
​
                    writelock.lock();
                    System.out.println(String.format("寫入執行緒%d:當前讀鎖數量:%d,是否寫鎖定:%b",this.tag,lock.getReadLockCount(),lock.isWriteLocked()));
                    System.out.println(String.format("寫入執行緒%d:進行寫入......剩餘次數:%d",this.tag,n));
                    writelock.unlock();
​
                }
​
            }
        }
​
        /*建立5個讀執行緒,5個寫執行緒*/
        int tlength = 5;
        Thread[] rthreads = new RThread[5];
        Thread[] wthreads = new WThread[5];
​
        for(int i=0;i<tlength;i++){
            rthreads[i] = new RThread(i,lock);
            rthreads[i].start();
            wthreads[i] = new WThread(i,lock);
            wthreads[i].start();
        }
​
        
​
    }

2.6 Condition

Condition將物件監視器方法(wait、notify和notifyAll)分解為不同的物件,Lock取代了synchronized,而Condition取代了物件監視器方法的使用。

Condition與Lock例項繫結,通過newCondition()方法建立。包含如下方法:

  • void await():呼叫後當前執行緒進入等待,直到被喚醒或者被中斷。

  • void awaitUninterruptibly():使當前執行緒等待直到被喚醒。該方法不會被中斷。

  • boolean await(long time, TimeUnit unit):使當前執行緒等待直到被喚醒或被中斷,或指定的等待時間過去,返回是否超時的判定。

  • long awaitNanos(long nanosTimeout):使當前執行緒等待直到被喚醒或中斷,或指定的等待時間過去,返回剩餘等待時間。

  • boolean awaitUntil(Date deadline):使當前執行緒等待直到發出訊號或中斷,或者指定的最後期限過去。

  • void signal():喚醒一個等待執行緒。

  • void signalAll():喚醒所有等待執行緒。

需要注意的是:除了呼叫Condition的等待及喚醒方法,包括lock的方法lock.hasWaiters(condition)和lock.getWaitQueueLength(condition),使用condition相關方法前必須已經獲得對應的lock,否則會報錯“java.lang.IllegalMonitorStateException”

測試程式碼如下:

    /* 測試Condition */
    public void test5(){
        /* 建立鎖和condition */
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
​
        /* 建立等待子執行緒 */
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
​
​
                lock.lock();
                System.out.println(Thread.currentThread().getName()+": 獲得鎖並進入等待。");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+": 被喚醒並獲得鎖,執行結束退出鎖。");
                lock.unlock();
            }
        };
        int tlength = 5;
        Thread[] threads = new Thread[tlength];
​
        for(int i=0;i<tlength;i++){
            threads[i] = new Thread(r1);
            threads[i].start();
        }
​
        /*  主執行緒進行喚醒執行緒 */
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
​
        lock.lock();
        System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d",
                lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
        System.out.println("主執行緒:signal()");
        condition.signal();
        System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d",
                lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
        System.out.println("主執行緒:signal()");
        condition.signal();
        System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d",
                lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
        System.out.println("主執行緒:signalAll()");
        condition.signalAll();
        System.out.println(String.format("主執行緒:等待鎖lock的執行緒數估計:%d, 是否有執行緒正在等待condition:%b,估計等待執行緒數:%d",
                lock.getQueueLength(),lock.hasWaiters(condition),lock.getWaitQueueLength(condition)));
​
        lock.unlock();
​
​
​
    }

執行結果如下:

Thread-0: 獲得鎖並進入等待。
Thread-2: 獲得鎖並進入等待。
Thread-3: 獲得鎖並進入等待。
Thread-1: 獲得鎖並進入等待。
Thread-4: 獲得鎖並進入等待。
主執行緒:等待鎖lock的執行緒數估計:0, 是否有執行緒正在等待condition:true,估計等待執行緒數:5
主執行緒:signal()
主執行緒:等待鎖lock的執行緒數估計:1, 是否有執行緒正在等待condition:true,估計等待執行緒數:4
主執行緒:signal()
主執行緒:等待鎖lock的執行緒數估計:2, 是否有執行緒正在等待condition:true,估計等待執行緒數:3
主執行緒:signalAll()
主執行緒:等待鎖lock的執行緒數估計:5, 是否有執行緒正在等待condition:false,估計等待執行緒數:0
Thread-0: 被喚醒並獲得鎖,執行結束退出鎖。
Thread-2: 被喚醒並獲得鎖,執行結束退出鎖。
Thread-3: 被喚醒並獲得鎖,執行結束退出鎖。
Thread-1: 被喚醒並獲得鎖,執行結束退出鎖。
Thread-4: 被喚醒並獲得鎖,執行結束退出鎖。

2.X 參考

Java中Lock,tryLock,lockInterruptibly有什麼區別? - wuxinliulei的回答 - 知乎 https://www.zhihu.com/question/36771163/answer/68974735

Java併發程式設計:Lock

當你深入瞭解,你就會發現世界如此廣袤,而你對世界的瞭解則是如此淺薄,請永遠保持謙卑的態度。