java并發(fā)等待條件的實現(xiàn)原理詳解
前言
前面介紹了排它鎖,共享鎖的實現(xiàn)機制,本篇繼續(xù)學習AQS中的另外一個內(nèi)容-Condition。想必學過java的都知道Object.wait和Object.notify,同時也應該知曉這兩個方法的使用離不開synchronized關鍵字。synchronized是jvm級別提供的同步原語,它的實現(xiàn)機制隱藏在jvm實現(xiàn)中。作為Lock系列功能中的Condition,就是用來實現(xiàn)類似 Object.wait和Object.notify 對應功能的。
使用場景
為了更好的理解Lock和Condition的使用場景,下面我們先來實現(xiàn)這樣一個功能:有多個生產(chǎn)者,多個消費者,一個產(chǎn)品容器,我們假設容器最多可以放3個產(chǎn)品,如果滿了,生產(chǎn)者需要等待產(chǎn)品被消費,如果沒有產(chǎn)品了,消費者需要等待。我們的目標是一共生產(chǎn)10個產(chǎn)品,最終消費10個產(chǎn)品,如何在多線程環(huán)境下完成這一挑戰(zhàn)呢?下面是我簡單實現(xiàn)的一個demo,僅供參考。
package com.lock.condition.test;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class LockConditionTest {
// 生產(chǎn) 和 消費 的最大總數(shù)
public static int totalCount = 10;
// 已經(jīng)生產(chǎn)的產(chǎn)品數(shù)
public static volatile int hasProduceCount = 0;
// 已經(jīng)消費的產(chǎn)品數(shù)
public static volatile int hasConsumeCount = 0;
// 容器最大容量
public static int containerSize = 3;
// 使用公平策略的可重入鎖,便于觀察演示結果
public static ReentrantLock lock = new ReentrantLock(true);
public static Condition notEmpty = lock.newCondition();
public static Condition notFull = lock.newCondition();
// 容器
public static LinkedList<integer> container = new LinkedList<integer>();
// 用于標識產(chǎn)品
public static AtomicInteger idGenerator = new AtomicInteger();
public static void main(String[] args) {
Thread p1 = new Thread(new Producer(), "p-1");
Thread p2 = new Thread(new Producer(), "p-2");
Thread p3 = new Thread(new Producer(), "p-3");
Thread c1 = new Thread(new Consumer(), "c-1");
Thread c2 = new Thread(new Consumer(), "c-2");
Thread c3 = new Thread(new Consumer(), "c-3");
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
try{
c1.join();
c2.join();
c3.join();
p1.join();
p2.join();
p3.join();
}catch(Exception e){
}
System.out.println(" done. ");
}
static class Producer implements Runnable{
@Override
public void run() {
while(true){
lock.lock();
try{
// 容器滿了,需要等待非滿條件
while(container.size() >= containerSize){
notFull.await();
}
// 到這里表明容器未滿,但需要再次判斷是否已經(jīng)完成了任務
if(hasProduceCount >= totalCount){
System.out.println(Thread.currentThread().getName()+" producer exit");
return ;
}
int product = idGenerator.incrementAndGet();
// 把生產(chǎn)出來的產(chǎn)品放入容器
container.addLast(product);
System.out.println(Thread.currentThread().getName() + " product " + product);
hasProduceCount++;
// 通知消費線程可以去消費了
notEmpty.signal();
} catch (InterruptedException e) {
}finally{
lock.unlock();
}
}
}
}
static class Consumer implements Runnable{
@Override
public void run() {
while(true){
lock.lock();
try{
if(hasConsumeCount >= totalCount){
System.out.println(Thread.currentThread().getName()+" consumer exit");
return ;
}
// 一直等待有產(chǎn)品了,再繼續(xù)往下消費
while(container.isEmpty()){
notEmpty.await(2, TimeUnit.SECONDS);
if(hasConsumeCount >= totalCount){
System.out.println(Thread.currentThread().getName()+" consumer exit");
return ;
}
}
Integer product = container.removeFirst();
System.out.println(Thread.currentThread().getName() + " consume " + product);
hasConsumeCount++;
// 通知生產(chǎn)線程可以繼續(xù)生產(chǎn)產(chǎn)品了
notFull.signal();
} catch (InterruptedException e) {
}finally{
lock.unlock();
}
}
}
}
}
一次執(zhí)行結果如下:
p-1 product 1 p-3 product 2 p-2 product 3 c-3 consume 1 c-2 consume 2 c-1 consume 3 p-1 product 4 p-3 product 5 p-2 product 6 c-3 consume 4 c-2 consume 5 c-1 consume 6 p-1 product 7 p-3 product 8 p-2 product 9 c-3 consume 7 c-2 consume 8 c-1 consume 9 p-1 product 10 p-3 producer exit p-2 producer exit c-3 consume 10 c-2 consumer exit c-1 consumer exit p-1 producer exit c-3 consumer exit done.
從結果可以發(fā)現(xiàn)已經(jīng)達到我們的目的了。
深入理解Condition的實現(xiàn)原理
上面的示例只是為了展示 Lock結合Condition可以實現(xiàn)的一種經(jīng)典場景,在有了感性的認識之后,我們將一步一步來觀察Lock和Condition是如何協(xié)作完成這一任務的,這也是本篇的核心內(nèi)容。
為了更好的理解和演示這一個過程,我們使用到的鎖是使用公平策略模式的,我們會使用上面例子運作的流程。我們會使用到3個生產(chǎn)線程,3個消費線程,分別表示 p1、p2、p3和c1、c2、c3。
Condition的內(nèi)部實現(xiàn)是使用節(jié)點鏈來實現(xiàn)的,每個條件實例對應一個節(jié)點鏈,我們有notEmpty 和 notFull 兩個條件實例,所以會有兩個等待節(jié)點鏈。
一切準備就緒 ,開始我們的探索之旅。
1、線程c3執(zhí)行,然后發(fā)現(xiàn)沒有產(chǎn)品可以消費,執(zhí)行 notEmpty.await,進入等待隊列中等候。

2、線程c2和線程c1執(zhí)行,然后發(fā)現(xiàn)沒有產(chǎn)品可以消費,執(zhí)行 notEmpty.await,進入等待隊列中等候。

3、 線程 p1 啟動,得到了鎖,p1開始生產(chǎn)產(chǎn)品,這時候p3搶在p2之前,執(zhí)行了lock操作,結果p2和p3都處于等待狀態(tài),入同步隊列等待。<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這里寫圖片描述" src="/uploadfile/Collfiles/20160912/20160912092710536.png" title="\" />
注意,本例中我們使用的是公平策略模式下的排它鎖,由于p3搶先執(zhí)行取鎖操作,所以雖然p2和p3都被阻塞了,但是p3會優(yōu)先被喚醒 。
4、這會,p1生產(chǎn)完畢,通知 not empty等待隊列,可以喚醒一個等待線程節(jié)點了,然后釋放了鎖,釋放鎖會導致p3被喚醒,然后p1進入下一個循環(huán),進入同步隊列。

事情開始變得有趣了,p1執(zhí)行一次生產(chǎn)后,執(zhí)行了 notEmpty.signal,其效果就是把 not empty等待列表中的頭節(jié)點,即c3節(jié)點移到同步等待列隊中,重新參與搶占鎖。
5、p3生產(chǎn)完了產(chǎn)品后,繼續(xù)notEmpty.signal,同時釋放鎖,釋放鎖后會喚醒p2線程,然后p3在下一輪嘗試獲取鎖的時候,再次入隊。

6、接著,p2繼續(xù)生產(chǎn),生產(chǎn)后執(zhí)行 notEmpty.signal,同時釋放鎖,釋放鎖后喚醒c3線程,然后p2在下一輪嘗試取鎖的時候,入列。

7、c3進行消費,你可以看到,現(xiàn)在 not empty等待列隊中已經(jīng)沒有等待節(jié)點了,由于我們使用的是公平策略排它鎖,這就會導致同步隊列中的節(jié)點一個接著一個執(zhí)行,而目前同步隊列中的節(jié)點排列為一生產(chǎn),一消費,這不難可以知道,接下來代碼已經(jīng)不會進入 wait條件了,所以一個一個輪流執(zhí)行就是,比如c3,執(zhí)行完了,繼續(xù)notFull.signal(); 然后釋放鎖,入隊,這里要明白,notFull.signal();這句代碼其實沒有作用了,因為 not full等待隊列中沒有任何等待線程節(jié)點。 c3執(zhí)行后,狀態(tài)如下圖所示:

8、后面的事情我想大家都可以想得出來是怎樣一步一步交替執(zhí)行的了。
總結
本篇基于一個實例來演示結合Lock和Condition如何實現(xiàn)生產(chǎn)-消費模式,而且只討論一種可能執(zhí)行的流程,是想更簡單的表述AQS底層是如何實現(xiàn)的?;谏厦孢@個演示過程,針對其它的執(zhí)行流程,其原來也是一樣的。Condition內(nèi)部使用一個節(jié)點鏈來保存所有 wait狀態(tài)的線程,當對應條件被signal的時候,就會把等待節(jié)點轉移到同步隊列中,繼續(xù)競爭鎖。原理其實并不復雜,有興趣的朋友可以翻閱源碼。
以上就是本文關于java并發(fā)等待條件的實現(xiàn)原理詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:java并發(fā)學習之BlockingQueue實現(xiàn)生產(chǎn)者消費者詳解、Java并發(fā)之嵌套管程鎖死詳解、Java系統(tǒng)的高并發(fā)解決方法詳解等,有什么問題可以隨時留言,小編會及時回復大家的。感謝朋友們對本站的支持!
相關文章
解決在IDEA中創(chuàng)建多級package的問題
這篇文章主要介紹了解決在IDEA中創(chuàng)建多級package的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Java/Android 實現(xiàn)簡單的HTTP服務器
這篇文章主要介紹了Java/Android 如何實現(xiàn)簡單的HTTP服務器,幫助大家更好的進行功能測試,感興趣的朋友可以了解下2020-10-10
Servlet的5種方式實現(xiàn)表單提交(注冊小功能),后臺獲取表單數(shù)據(jù)實例
這篇文章主要介紹了Servlet的5種方式實現(xiàn)表單提交(注冊小功能),后臺獲取表單數(shù)據(jù)實例,非常具有實用價值,需要的朋友可以參考下2017-05-05
Java Mybatis中的 ${ } 和 #{ }的區(qū)別使用詳解
這篇文章主要介紹了Mybatis中的 ${ } 和 #{ }的區(qū)別使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07

