文章出處

一、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三個線程一直按照順序執行。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()