之前在介紹線程的狀態(tài)及轉(zhuǎn)換中旺芽,提到了Object有wait()罐柳、wait(long timeout)祟同、notify()、notifyAll()方法厢蒜,這些方法與synchronized配合使用霞势,可以實(shí)現(xiàn)等待/通知模式烹植。無(wú)獨(dú)有偶,Conditon接口也提供了類似Object的監(jiān)視器方法愕贡,與Lock配合也能實(shí)現(xiàn)通知/等待模式草雕。
1、Condition接口
①接口介紹
在上篇顯式鎖(一)介紹Lock接口時(shí)固以,Lock有一個(gè)方法叫" Condition newCondition(); "墩虹,此方法是獲取等待通知組件,也就是Conditon憨琳,獲取的Condition與此Lock綁定诫钓。也可以說(shuō),Conditon是依賴Lock對(duì)象篙螟。
java-8
public interface Condition {
//當(dāng)前線程進(jìn)入等待狀態(tài)直到被通知或中斷
void await() throws InterruptedException;
//當(dāng)前線程進(jìn)入等待狀態(tài)直到被通知菌湃,對(duì)中斷不敏感
void awaitUninterruptibly();
//當(dāng)前線程進(jìn)入等待狀態(tài)直到被通知、中斷或超時(shí)
long awaitNanos(long nanosTimeout) throws InterruptedException;
//當(dāng)前線程進(jìn)入等待狀態(tài)直到被通知遍略、中斷惧所,在指定時(shí)間內(nèi)被通知返回true,否則false
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
//喚醒一個(gè)等待在Condition上的線程
void signal();
//喚醒所有等待在Conditon上的線程
void signalAll();
}
一般的使用套路如下:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException{
lock.lock();
try{
condition.await();
}finally{
lock.unlock();
}
}
public void conditionSingal() throws InterruptedException{
lock.lock();
try{
condition.singal();
}finally{
lock.unlock();
}
}
②使用示例
通過(guò)一個(gè)生產(chǎn)者/消費(fèi)者的例子來(lái)使用下Condition:
/**
* Created by bxw on 2017/10/5.
*/
public class Depot {
private int capacity;
private int size;
private Lock lock;
private Condition consumerCond;
private Condition produceCond;
public Depot(int capacity) {
this.capacity = capacity;
this.size = 0;
this.lock = new ReentrantLock();
this.consumerCond = lock.newCondition();
this.produceCond = lock.newCondition();
}
public void produce(int val){
lock.lock();
try {
int left = val;
while(left > 0){
while(size >= capacity){
produceCond.await();
}
int produce = (left + size) > capacity ? (capacity - size) : left;
size += produce;
left -= produce;
System.out.println(Thread.currentThread().getName() + ", ProcudeVal=" + val +", produce=" + produce + ",size=" + size);
consumerCond.signalAll();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void consumer(int val){
lock.lock();
try {
int left = val;
while (left > 0) {
while (size <= 0) {
consumerCond.await();
}
int consumer = (size <= left) ? size : left;
size -= consumer;
left -= consumer;
System.out.println(Thread.currentThread().getName() + ", ConsumerVal=" + val + ", consumer=" + consumer + ", size=" + size);
produceCond.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class Producer {
private Depot depot;
public Producer(Depot depot) {
this.depot = depot;
}
public void produceThing(final int amount) {
new Thread(new Runnable() {
public void run() {
depot.produce(amount);
}
}).start();
}
}
public class Consumer {
private Depot depot;
public Consumer(Depot depot) {
this.depot = depot;
}
public void consumerThing(final int amount) {
new Thread(new Runnable() {
public void run() {
depot.consumer(amount);
}
}).start();
}
}
public class Test {
public static void main(String[] args) {
// 倉(cāng)庫(kù)
Depot depot = new Depot(100);
// 消費(fèi)者
Consumer consumer = new Consumer(depot);
// 生產(chǎn)者
Producer produce = new Producer(depot);
produce.produceThing(5);
consumer.consumerThing(5);
produce.produceThing(2);
consumer.consumerThing(5);
produce.produceThing(5);
}
}
運(yùn)行結(jié)果:
從結(jié)果看出,Thread-0和Thread-1比較和諧绪杏,Thread-0生產(chǎn)了5個(gè)纯路,Thread-1消費(fèi)了5個(gè),剩余0寞忿。接著Thread-2又生產(chǎn)了2個(gè)驰唬,Thread-3需要消費(fèi)5個(gè),但倉(cāng)庫(kù)中只有2個(gè)腔彰,所以先將庫(kù)存的2個(gè)產(chǎn)品全部消費(fèi)叫编,然后這個(gè)線程進(jìn)入等待隊(duì)列,等待生產(chǎn)霹抛,隨后生產(chǎn)出了5個(gè)產(chǎn)品搓逾,生產(chǎn)者生產(chǎn)后又執(zhí)行signalAll方法將等待隊(duì)列中所有的線程都喚醒,Thread-3繼續(xù)消費(fèi)還需要的3個(gè)產(chǎn)品杯拐,倉(cāng)庫(kù)還剩余了2個(gè)霞篡。
③實(shí)現(xiàn)分析
首先從Lock.newCondition分析,以ReentrantLock為例端逼,會(huì)返回一個(gè)ConditionObject對(duì)象朗兵。
那ConditionObject為何物,原來(lái)它是AbstractQueuedSynchronizer(簡(jiǎn)稱AQS)的一個(gè)內(nèi)部類顶滩,擁有兩個(gè)節(jié)點(diǎn):頭節(jié)點(diǎn)余掖、尾節(jié)點(diǎn)。
因?yàn)槊總€(gè)ConditionObject都包含一個(gè)等待隊(duì)列礁鲁,這是Conditon實(shí)現(xiàn)等待/通知的關(guān)鍵盐欺。等待隊(duì)列是個(gè)FIFO隊(duì)列赁豆,隊(duì)列中每個(gè)節(jié)點(diǎn)都包含了一個(gè)線程的引用,該線程是在Condition對(duì)象上等待的線程冗美,如果一個(gè)線程調(diào)用了Condition.await()方法魔种,該線程就釋放鎖、構(gòu)造節(jié)點(diǎn)加入等待隊(duì)列并進(jìn)入等待狀態(tài)粉洼。節(jié)點(diǎn)(Node)的定義也是AQS中的一個(gè)內(nèi)部類务嫡。
Condition擁有首節(jié)點(diǎn)的引用,新增節(jié)點(diǎn)只需要把原來(lái)的尾節(jié)點(diǎn)nextWaiter指向它漆改,并且更新尾節(jié)點(diǎn)即可心铃。
在Object的監(jiān)視器模型上,一個(gè)對(duì)象擁有一個(gè)同步隊(duì)列和等待隊(duì)列挫剑,而Lock(同步器)擁有一個(gè)同步隊(duì)列和多個(gè)等待隊(duì)列關(guān)系圖如下:
從隊(duì)列角度看去扣,調(diào)用Condition的await()方法,就是把同步隊(duì)列的首節(jié)點(diǎn)移動(dòng)到了Condition的等待隊(duì)列樊破,線程狀態(tài)變?yōu)榈却隣顟B(tài)愉棱。
總結(jié)
此篇主要介紹了依賴Lock的Condition,Condition的等待/通知模型和Object的等待/通知模型有些相似哲戚,兩者可以對(duì)比著使用奔滑,分析了Condition的簡(jiǎn)單實(shí)現(xiàn)原理,引出了隊(duì)列同步器的概念顺少,下篇就簡(jiǎn)單介紹下AQS朋其。
參考書(shū)籍《并發(fā)編程的藝術(shù)》,
案例參考博客 http://www.cnblogs.com/zhengbin/p/6420984.html
略陳固陋脆炎,如有不當(dāng)之處梅猿,歡迎各位看官批評(píng)指正!