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

 

0、JAVA多線程編程

Java多線程編程所涉及的知識點包含線程創建、線程同步、線程間通信、線程死鎖、線程控制(掛起、停止和恢復)。之前 JAVA篇:Java的線程僅僅了解了部分線程創建和同步相關的小部分知識點,但是其實在編程過程中遇到的事情並不僅僅限於此,所以進行整理,列表如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM