1. 程式人生 > 實用技巧 >Java——多執行緒之ReentrantLock與Condition(轉)

Java——多執行緒之ReentrantLock與Condition(轉)

轉自:https://www.cnblogs.com/xiaoxi/p/7651360.html

一、ReentrantLock

1、ReentrantLock簡介

ReentrantLock是一個可重入的互斥鎖,又被稱為“獨佔鎖”。ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和記憶體語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的效能。(換句話說,當許多執行緒都想訪問共享資源時,JVM 可以花更少的時候來排程執行緒,把更多時間用在執行執行緒上。)

顧名思義,ReentrantLock鎖在同一個時間點只能被一個執行緒鎖持有;而可重入的意思是,ReentrantLock鎖,可以被單個執行緒多次獲取。ReentrantLock分為“公平鎖

”和“非公平鎖”。它們的區別體現在獲取鎖的機制上是否公平。“鎖”是為了保護競爭資源,防止多個執行緒同時操作執行緒而出錯,ReentrantLock在同一個時間點只能被一個執行緒獲取(當某執行緒獲取到“鎖”時,其它執行緒就必須等待);ReentraantLock是通過一個FIFO的等待佇列來管理獲取該鎖所有執行緒的。在“公平鎖”的機制下,執行緒依次排隊獲取鎖;而“非公平鎖”在鎖是可獲取狀態時,不管自己是不是在佇列的開頭都會獲取鎖。

2、ReentrantLock函式列表

// 建立一個 ReentrantLock ,預設是“非公平鎖”。
ReentrantLock()
// 建立策略是fair的 ReentrantLock。fair為true表示是公平鎖,fair為false表示是非公平鎖。
ReentrantLock(boolean fair)

// 查詢當前執行緒保持此鎖的次數。
int getHoldCount()
// 返回目前擁有此鎖的執行緒,如果此鎖不被任何執行緒擁有,則返回 null。
protected Thread getOwner()
// 返回一個 collection,它包含可能正等待獲取此鎖的執行緒。
protected Collection<Thread> getQueuedThreads()
// 返回正等待獲取此鎖的執行緒估計數。
int getQueueLength()
// 返回一個 collection,它包含可能正在等待與此鎖相關給定條件的那些執行緒。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回等待與此鎖相關的給定條件的執行緒估計數。
int getWaitQueueLength(Condition condition)
// 查詢給定執行緒是否正在等待獲取此鎖。
boolean hasQueuedThread(Thread thread)
// 查詢是否有些執行緒正在等待獲取此鎖。
boolean hasQueuedThreads()
// 查詢是否有些執行緒正在等待與此鎖有關的給定條件。
boolean hasWaiters(Condition condition)
// 如果是“公平鎖”返回true,否則返回false。
boolean isFair()
// 查詢當前執行緒是否保持此鎖。
boolean isHeldByCurrentThread()
// 查詢此鎖是否由任意執行緒保持。
boolean isLocked()
// 獲取鎖。
void lock()
// 如果當前執行緒未被中斷,則獲取鎖。
void lockInterruptibly()
// 返回用來與此 Lock 例項一起使用的 Condition 例項。
Condition newCondition()
// 僅在呼叫時鎖未被另一個執行緒保持的情況下,才獲取該鎖。
boolean tryLock()
// 如果鎖在給定等待時間內沒有被另一個執行緒保持,且當前執行緒未被中斷,則獲取該鎖。
boolean tryLock(long timeout, TimeUnit unit)
// 試圖釋放此鎖。
void unlock()

可重入鎖指在同一個執行緒中,可以重入的鎖。當然,當這個執行緒獲得鎖後,其他執行緒將等待這個鎖被釋放後,才可以獲得這個鎖。

通常的使用方法:

ReentrantLock lock = new ReentrantLock(); // not a fair lock
lock.lock();

try {

    // synchronized do something

} finally {
    lock.unlock();
}

3、重入的實現

reentrant 鎖意味著什麼呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個執行緒再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果執行緒進入由執行緒已經擁有的監控器保護的 synchronized 塊,就允許執行緒繼續進行,當執行緒退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有執行緒退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

對於鎖的重入,我們來想這樣一個場景。當一個遞迴方法被sychronized關鍵字修飾時,在呼叫方法時顯然沒有發生問題,執行執行緒獲取了鎖之後仍能連續多次地獲得該鎖,也就是說sychronized關鍵字支援鎖的重入。對於ReentrantLock,雖然沒有像sychronized那樣隱式地支援重入,但在呼叫lock()方法時,已經獲取到鎖的執行緒,能夠再次呼叫lock()方法獲取鎖而不被阻塞。

如果想要實現鎖的重入,至少要解決一下兩個問題:

  • 執行緒再次獲取鎖:鎖需要去識別獲取鎖的執行緒是否為當前佔據鎖的執行緒,如果是,則再次成功獲取。
  • 鎖的最終釋放:執行緒重複n次獲取了鎖,隨後在n次釋放該鎖後,其他執行緒能夠獲取該鎖。鎖的最終釋放要求鎖對於獲取進行計數自增,計數表示當前鎖被重複獲取的次數,而鎖被釋放時,計數自減,當計數等於0時表示鎖已經釋放。

4、公平鎖與非公平鎖

在Java的ReentrantLock建構函式中提供了兩種鎖:建立公平鎖和非公平鎖(預設)。程式碼如下:

/**
 * 預設構造方法,非公平鎖
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * true公平鎖,false非公平鎖
 * @param fair
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

如果獲取一個鎖是按照請求的順序得到的,那麼就是公平鎖,否則就是非公平鎖。

在沒有深入瞭解內部機制及實現之前,先了解下為什麼會存在公平鎖和非公平鎖。公平鎖保證一個阻塞的執行緒最終能夠獲得鎖,因為是有序的,所以總是可以按照請求的順序獲得鎖。非公平鎖意味著後請求鎖的執行緒可能在其前面排列的休眠執行緒恢復前拿到鎖,這樣就有可能提高併發的效能。這是因為通常情況下掛起的執行緒重新開始與它真正開始執行,二者之間會產生嚴重的延時。因此非公平鎖就可以利用這段時間完成操作。這是非公平鎖在某些時候比公平鎖效能要好的原因之一。

鎖Lock分為“公平鎖”和“非公平鎖”,公平鎖表示執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,即先來先得的FIFO先進先出順序。而非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖,這個方式可能造成某些執行緒一直拿不到鎖,結果也就是不公平的了。

5、ReentrantLock 擴充套件的功能

(1)實現可輪詢的鎖請求

在內部鎖中,死鎖是致命的——唯一的恢復方法是重新啟動程式,唯一的預防方法是在構建程式時不要出錯。而可輪詢的鎖獲取模式具有更完善的錯誤恢復機制,可以規避死鎖的發生。

如果你不能獲得所有需要的鎖,那麼使用可輪詢的獲取方式使你能夠重新拿到控制權,它會釋放你已經獲得的這些鎖,然後再重新嘗試。可輪詢的鎖獲取模式,由tryLock()方法實現。此方法僅在呼叫時鎖為空閒狀態才獲取該鎖。如果鎖可用,則獲取鎖,並立即返回值true。如果鎖不可用,則此方法將立即返回值false。

(2)實現可定時的鎖請求

當使用內部鎖時,一旦開始請求,鎖就不能停止了,所以內部鎖給實現具有時限的活動帶來了風險。為了解決這一問題,可以使用定時鎖。當具有時限的活
動呼叫了阻塞方法,定時鎖能夠在時間預算內設定相應的超時。如果活動在期待的時間內沒能獲得結果,定時鎖能使程式提前返回。可定時的鎖獲取模式,由tryLock(long, TimeUnit)方法實現。

(3)實現可中斷的鎖獲取請求

可中斷的鎖獲取操作允許在可取消的活動中使用。lockInterruptibly()方法能夠使你獲得鎖的時候響應中斷。

6、ReentrantLock 與 synchronized 的比較

相同:ReentrantLock提供了synchronized類似的功能和記憶體語義。

不同:

(1)與synchronized相比,ReentrantLock提供了更多,更加全面的功能,具備更強的擴充套件性。例如:時間鎖等候,可中斷鎖等候,鎖投票。

(2)ReentrantLock還提供了條件Condition,對執行緒的等待、喚醒操作更加詳細和靈活,所以在多個條件變數和高度競爭鎖的地方,ReentrantLock更加適合(下面會闡述Condition)。

(3)ReentrantLock提供了可輪詢的鎖請求。它會嘗試著去獲取鎖,如果成功則繼續,否則可以等到下次執行時處理,而synchronized則一旦進入鎖請求要麼成功,要麼一直阻塞,所以相比synchronized而言,ReentrantLock會不容易產生死鎖些。

(4)ReentrantLock支援更加靈活的同步程式碼塊,但是使用synchronized時,只能在同一個synchronized塊結構中獲取和釋放。注:ReentrantLock的鎖釋放一定要在finally中處理,否則可能會產生嚴重的後果。

(5)ReentrantLock支援中斷處理,且效能較synchronized會好些。

7、ReentrantLock 不好與需要注意的地方

(1)lock 必須在 finally 塊中釋放。否則,如果受保護的程式碼將丟擲異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什麼,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程式中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。

(2) 當 JVM 用 synchronized 管理鎖定請求和釋放時,JVM 在生成執行緒轉儲時能夠包括鎖定資訊。這些對除錯非常有價值,因為它們能標識死鎖或者其他異常行為的來源。 Lock 類只是普通的類,JVM 不知道具體哪個執行緒擁有 Lock 物件。

8、示例分析

示例1:

首先來看第一個例項:用兩個執行緒在控制檯有序打出1,2,3。

package com.demo.test;

public class FirstReentrantLock {

    public static void main(String[] args) {
        Runnable runnable = new ReentrantLockThread();
        new Thread(runnable, "a").start();
        new Thread(runnable, "b").start();
    }
}
package com.demo.test;

public class ReentrantLockThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "輸出了:  " + i);
        }
    }
}

執行FirstReentrantLock ,檢視控制檯輸出:

a輸出了:  0
b輸出了:  0
a輸出了:  1
b輸出了:  1
a輸出了:  2
b輸出了:  2

可以看到,並沒有順序,雜亂無章。

那使用ReentrantLock加入鎖,程式碼如下:

package com.demo.test;

/**
 * 如何使用ReentrantLock
 * @author lxx
 *
 */
public class FirstReentrantLock {

    public static void main(String[] args) {
        Runnable runnable = new ReentrantLockThread();
        new Thread(runnable, "a").start();
        new Thread(runnable, "b").start();
    }
}
package com.demo.test;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockThread implements Runnable{
    
    // 建立一個ReentrantLock物件
    ReentrantLock lock = new ReentrantLock();
    
    @Override
    public void run() {
        try{
            // 使用lock()方法加鎖
            lock.lock();
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + "輸出了:  " + i);
            }
        }finally{
            // 別忘了執行unlock()方法釋放鎖
            lock.unlock();
        }
        
    }
}

執行FirstReentrantLock ,檢視控制檯輸出:

a輸出了:  0
a輸出了:  1
a輸出了:  2
b輸出了:  0
b輸出了:  1
b輸出了:  2

有順序的打印出了0,1,2。這就是鎖的作用,它是互斥的,當一個執行緒持有鎖的時候,其他執行緒只能等待,待該執行緒執行結束,再通過競爭得到鎖。

示例2:測試可重入鎖的重入特性。

package com.demo.test;

import java.util.Calendar;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    private ReentrantLock lock = null;

    public TestLock() {
        // 建立一個自由競爭的可重入鎖
        lock = new ReentrantLock();
    }

    public static void main(String[] args) {

        TestLock tester = new TestLock();
        
        try{
            // 測試可重入,方法testReentry() 在同一執行緒中,可重複獲取鎖,執行獲取鎖後,顯示資訊的功能
            tester.testReentry();
            // 能執行到這裡而不阻塞,表示鎖可重入
            tester.testReentry();
            // 再次重入
            tester.testReentry();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            // 釋放重入測試的鎖,要按重入的數量解鎖,否則其他執行緒無法獲取該鎖。
            tester.getLock().unlock();
            tester.getLock().unlock();
            tester.getLock().unlock();

        }
    }

    public ReentrantLock getLock() {
        return lock;
    }

    public void testReentry() {
        lock.lock();

        Calendar now = Calendar.getInstance();

        System.out.println(now.getTime() + " " + Thread.currentThread().getName()
                + " get lock.");
    }

}

執行結果:

Thu Oct 12 22:01:47 CST 2017 main get lock.
Thu Oct 12 22:01:47 CST 2017 main get lock.
Thu Oct 12 22:01:47 CST 2017 main get lock.

示例3:此例可反應公平鎖和非公平鎖的差異。

(1)公平鎖

package com.demo.test;

import java.util.concurrent.locks.ReentrantLock;

public class Service {

    private ReentrantLock lock ;  
    
    public Service(boolean isFair) {  
        lock = new ReentrantLock(isFair);  
    }  
  
    public void serviceMethod() {  
        try {  
            lock.lock();  
            System.out.println("ThreadName=" + Thread.currentThread().getName()  
                    + " 獲得鎖定");  
        } finally {  
            lock.unlock();  
        }  
    }
}
package com.demo.test;

public class Run {

    public static void main(String[] args) throws InterruptedException {  
        final Service service = new Service(true);  //改為false就為非公平鎖了  
        Runnable runnable = new Runnable() {  
            public void run() {  
                System.out.println("**執行緒: " + Thread.currentThread().getName()  
                        +  " 運行了 " );  
                service.serviceMethod();  
            }  
        };  
  
        Thread[] threadArray = new Thread[10];  
  
        for (int i=0; i<10; i++) {  
            threadArray[i] = new Thread(runnable);  
        }  
        for (int i=0; i<10; i++) {  
            threadArray[i].start();  
        }  
    }  
}

執行結果:

**執行緒: Thread-0 運行了 
**執行緒: Thread-2 運行了 
ThreadName=Thread-0 獲得鎖定
**執行緒: Thread-4 運行了 
**執行緒: Thread-3 運行了 
**執行緒: Thread-6 運行了 
ThreadName=Thread-2 獲得鎖定
**執行緒: Thread-8 運行了 
ThreadName=Thread-4 獲得鎖定
**執行緒: Thread-7 運行了 
ThreadName=Thread-3 獲得鎖定
**執行緒: Thread-1 運行了 
ThreadName=Thread-6 獲得鎖定
ThreadName=Thread-8 獲得鎖定
**執行緒: Thread-9 運行了 
**執行緒: Thread-5 運行了 
ThreadName=Thread-7 獲得鎖定
ThreadName=Thread-1 獲得鎖定
ThreadName=Thread-9 獲得鎖定
ThreadName=Thread-5 獲得鎖定

列印的結果是按照執行緒加鎖的順序輸出的,即執行緒運行了,則會先獲得鎖。

(2)非公平鎖

將下面語句中的引數true改為false就為非公平鎖了。

final Service service = new Service(true);  //改為false就為非公平鎖了  

執行結果:

**執行緒: Thread-1 運行了 
**執行緒: Thread-2 運行了 
ThreadName=Thread-2 獲得鎖定
ThreadName=Thread-1 獲得鎖定
**執行緒: Thread-6 運行了 
ThreadName=Thread-6 獲得鎖定
**執行緒: Thread-7 運行了 
ThreadName=Thread-7 獲得鎖定
**執行緒: Thread-0 運行了 
ThreadName=Thread-0 獲得鎖定
**執行緒: Thread-4 運行了 
**執行緒: Thread-9 運行了 
**執行緒: Thread-5 運行了 
**執行緒: Thread-3 運行了 
ThreadName=Thread-4 獲得鎖定
**執行緒: Thread-8 運行了 
ThreadName=Thread-8 獲得鎖定
ThreadName=Thread-9 獲得鎖定
ThreadName=Thread-5 獲得鎖定
ThreadName=Thread-3 獲得鎖定

是亂序的,說明先start()啟動的執行緒不代表先獲得鎖。

執行結果反映:

在公平的鎖上,執行緒按照他們發出請求的順序獲取鎖,但在非公平鎖上,則允許“插隊”:當一個執行緒請求非公平鎖時,如果在發出請求的同時該鎖變成可用狀態,那麼這個執行緒會跳過佇列中所有的等待執行緒而獲得鎖。非公平的ReentrantLock 並不提倡 插隊行為,但是無法防止某個執行緒在合適的時候進行插隊。

在公平的鎖中,如果有另一個執行緒持有鎖或者有其他執行緒在等待佇列中等待這個鎖,那麼新發出的請求的執行緒將被放入到佇列中。而非公平鎖上,只有當鎖被某個執行緒持有時,新發出請求的執行緒才會被放入佇列中。

非公平鎖效能高於公平鎖效能的原因:在恢復一個被掛起的執行緒與該執行緒真正執行之間存在著嚴重的延遲。假設執行緒A持有一個鎖,並且執行緒B請求這個鎖。由於鎖被A持有,因此B將被掛起。當A釋放鎖時,B將被喚醒,因此B會再次嘗試獲取這個鎖。與此同時,如果執行緒C也請求這個鎖,那麼C很可能會在B被完全喚醒之前獲得、使用以及釋放這個鎖。這樣就是一種雙贏的局面:B獲得鎖的時刻並沒有推遲,C更早的獲得了鎖,並且吞吐量也提高了。

當持有鎖的時間相對較長或者請求鎖的平均時間間隔較長,應該使用公平鎖。在這些情況下,插隊帶來的吞吐量提升(當鎖處於可用狀態時,執行緒卻還處於被喚醒的過程中)可能不會出現。

二、Condition

Condition是在java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現執行緒間的協作,相比使用Object的wait()、notify(),使用Condition的await()、signal()這種方式實現執行緒間協作更加安全和高效。因此通常來說比較推薦使用Condition。

Condition類能實現synchronized和wait、notify搭配的功能,另外比後者更靈活,Condition可以實現多路通知功能,也就是在一個Lock物件裡可以建立多個Condition(即物件監視器)例項,執行緒物件可以註冊在指定的Condition中,從而可以有選擇的進行執行緒通知,在排程執行緒上更加靈活。而synchronized就相當於整個Lock物件中只有一個單一的Condition物件,所有的執行緒都註冊在這個物件上。執行緒開始notifyAll時,需要通知所有的WAITING執行緒,沒有選擇權,會有相當大的效率問題。

1、Condition是個介面,基本的方法就是await()和signal()方法。

2、Condition依賴於Lock介面,生成一個Condition的基本程式碼是lock.newCondition(),參考下圖。

3、呼叫Condition的await()和signal()方法,都必須在lock保護之內,就是說必須在lock.lock()和lock.unlock之間才可以使用。

4、Conditon中的await()對應Object的wait(),Condition中的signal()對應Object的notify(),Condition中的signalAll()對應Object的notifyAll()。

接下來,使用Condition來實現等待/喚醒,並且能夠喚醒制定執行緒。

先寫業務程式碼:

package com.demo.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class MyService {
    
    // 例項化一個ReentrantLock物件
    private ReentrantLock lock = new ReentrantLock();
    // 為執行緒A註冊一個Condition
    public Condition conditionA = lock.newCondition();
    // 為執行緒B註冊一個Condition
    public Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "進入了awaitA方法");
            long timeBefore = System.currentTimeMillis();
            // 執行conditionA等待
            conditionA.await();
            long timeAfter = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName()+"被喚醒");
            System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "進入了awaitB方法");
            long timeBefore = System.currentTimeMillis();
            // 執行conditionB等待
            conditionB.await();
            long timeAfter = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName()+"被喚醒");
            System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signallA() {
        try {
            lock.lock();
            System.out.println("啟動喚醒程式");
            // 喚醒所有註冊conditionA的執行緒
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }
    
    public void signallB() {
        try {
            lock.lock();
            System.out.println("啟動喚醒程式");
            // 喚醒所有註冊conditionB的執行緒
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

分別例項化了兩個Condition物件,都是使用同一個lock註冊。注意conditionA物件的等待和喚醒只對使用了conditionA的執行緒有用,同理conditionB物件的等待和喚醒只對使用了conditionB的執行緒有用。

繼續寫兩個執行緒的程式碼:

package com.demo.test;

public class MyServiceThread1 implements Runnable{

    private MyService service;

    public MyServiceThread1(MyService service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.awaitA();
    }
}

注意:MyServiceThread1 使用了awaitA()方法,持有的是conditionA!

package com.demo.test;

public class MyServiceThread2 implements Runnable{

    private MyService service;

    public MyServiceThread2(MyService service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.awaitB();
    }
}

注意:MyServiceThread2 使用了awaitB()方法,持有的是conditionB!

最後看啟動類:

package com.demo.test;

public class ApplicationCondition {

    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        Runnable runnable1 = new MyServiceThread1(service);
        Runnable runnable2 = new MyServiceThread2(service);
        
        new Thread(runnable1, "a").start();
        new Thread(runnable2, "b").start();
        
        // 執行緒sleep2秒鐘
        Thread.sleep(2000);
        // 喚醒所有持有conditionA的執行緒
        service.signallA();
        
        Thread.sleep(2000);
        // 喚醒所有持有conditionB的執行緒
        service.signallB();
    }

}

執行ApplicationCondition,來看控制檯輸出結果:

a和b都進入各自的await()方法。首先執行的是

Thread.sleep(2000);
 // 喚醒所有持有conditionA的執行緒
service.signallA();

使用conditionA的執行緒被喚醒,而後再喚醒使用conditionB的執行緒。學會使用Condition,那來用它實現生產者消費者模式。

生產者和消費者

首先來看業務類的實現:

package com.demo.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PCService {

    private Lock lock = new ReentrantLock();
    private boolean flag = false;
    private Condition condition = lock.newCondition();
    // 以此為衡量標誌
    private int number = 1;

    /**
     * 生產者生產
     */
    public void produce() {
        try {
            lock.lock();
            while (flag == true) {
                condition.await();
            }
            System.out.println(Thread.currentThread().getName() + "-----生產-----");
            number++;
            System.out.println("number: " + number);
            System.out.println();
            flag = true;
            // 提醒消費者消費
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 消費者消費生產的物品
     */
    public void consume() {
        try {
            lock.lock();
            while (flag == false) {
                condition.await();
            }
            System.out.println(Thread.currentThread().getName() + "-----消費-----");
            number--;
            System.out.println("number: " + number);
            System.out.println();
            flag = false;
            // 提醒生產者生產
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

生產者執行緒程式碼:

package com.demo.test;

/**
 * 生產者執行緒
 * @author lixiaoxi
 *
 */
public class MyThreadProduce implements Runnable{

    private PCService service;

    public MyThreadProduce(PCService service) {
        this.service = service;
    }

    @Override
    public void run() {
        for (;;) {
            service.produce();
        }
    }

}

消費者執行緒程式碼:

package com.demo.test;

public class MyThreadConsume implements Runnable{

    private PCService service;

    public MyThreadConsume(PCService service) {
        this.service = service;
    }

    @Override
    public void run() {
        for (;;) {
            service.consume();
        }
    }    
}

啟動類:

package com.demo.test;

public class PCApplication {

    public static void main(String[] args) {
        PCService service = new PCService();
        Runnable produce = new MyThreadProduce(service);
        Runnable consume = new MyThreadConsume(service);
        new Thread(produce, "生產者  ").start();
        new Thread(consume, "消費者  ").start();
    }
}

執行PCApplication,看控制檯的輸出:

因為採用了無限迴圈,生產者執行緒和消費者執行緒會一直處於工作狀態,可以看到,生產者執行緒執行完畢後,消費者執行緒就會執行,以這樣的交替順序,而且number也遵循著生產者生產+1,消費者消費-1的一個狀態。這個就是使用ReentrantLock和Condition來實現的生產者消費者模式。

順序執行執行緒

充分發掘Condition的靈活性,可以用它來實現順序執行執行緒。

來看業務類程式碼:

package com.demo.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionService {

    // 通過nextThread控制下一個執行的執行緒
    private static int nextThread = 1;
    private ReentrantLock lock = new ReentrantLock();
    // 有三個執行緒,所以註冊三個Condition
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void excuteA() {
        try {
            lock.lock();
            while (nextThread != 1) {
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName() + " 工作");
            nextThread = 2;
            conditionB.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void excuteB() {
        try {
            lock.lock();
            while (nextThread != 2) {
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName() + " 工作");
            nextThread = 3;
            conditionC.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void excuteC() {
        try {
            lock.lock();
            while (nextThread != 3) {
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName() + " 工作");
            nextThread = 1;
            conditionA.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

這裡可以看到,註冊了三個Condition,分別用於三個執行緒的等待和通知。

啟動類程式碼:

package com.demo.test;

/**
 * 執行緒按順序執行
 * @author lixiaoxi
 *
 */
public class ConditionApplication {

    private static Runnable getThreadA(final ConditionService service) {
        return new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    service.excuteA();
                }
            }
        };
    }

    private static Runnable getThreadB(final ConditionService service) {
        return new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    service.excuteB();
                }
            }
        };
    }

    private static Runnable getThreadC(final ConditionService service) {
        return new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    service.excuteC();
                }
            }
        };
    }

    public static void main(String[] args) throws InterruptedException{
        ConditionService service = new ConditionService();
        Runnable A = getThreadA(service);
        Runnable B = getThreadB(service);
        Runnable C = getThreadC(service);

        new Thread(A, "A").start();
        new Thread(B, "B").start();
        new Thread(C, "C").start();
    }

}

執行啟動類,檢視控制檯輸出結果:

A,B,C三個執行緒一直按照順序執行。