鎖機(jī)制之 Condition 接口
1. 前言
本節(jié)內(nèi)容主要是對(duì) Java 鎖機(jī)制之 Condition 接口進(jìn)行講解,Condition 接口是配合 Lock 接口使用的,我們已經(jīng)學(xué)習(xí)過 Lock 接口的相關(guān)知識(shí),那么接下來對(duì) Condition 接口進(jìn)行講解。本節(jié)內(nèi)容的知識(shí)點(diǎn)如下:
- Condition 接口簡(jiǎn)介,這是我們認(rèn)識(shí) Condition 接口的基礎(chǔ);
- Condition 接口定義,整體上先了解 Condition 接口所包含的方法,為基礎(chǔ)內(nèi)容;
- Condition 接口所提供的方法與 Object 所提供的方法的區(qū)別與聯(lián)系,此部分為本節(jié)的重點(diǎn)之一;
- Condition 對(duì)象的創(chuàng)建方式,這是開始使用 Condition 的前提,需要牢記;
- Condition 接口的常用方法使用,這是本節(jié)課程的核心內(nèi)容,掌握 Condition 接口使用方式,也是我們本節(jié)課程的最終目的所在;
- 使用 ReentrantLock 與 Condition 接口,實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者模式。
2. Condition 接口簡(jiǎn)介
任意一個(gè) Java 對(duì)象,都擁有一組監(jiān)視器方法(定義在 java.lang.Object 上),主要包括 wait ()、wait (long timeout)、notify () 以及 notifyAll () 方法。這些方法與 synchronized 同步關(guān)鍵字配合,可以實(shí)現(xiàn)等待 / 通知模式。
定義:Condition 接口也提供了類似 Object 的監(jiān)視器方法,與 Lock 配合可以實(shí)現(xiàn)等待 / 通知模式。Condition 可以看做是 Obejct 類的 wait ()、notify ()、notifyAll () 方法的替代品,與 Lock 配合使用。
3. Condition 接口定義
我們看到,從 JDK 的源碼中可以獲悉,Condition 接口包含了如下的方法,對(duì)于其中常用的方法,我們?cè)诤罄m(xù)的內(nèi)容中會(huì)有比較詳細(xì)的講解。
public interface Condition {
void await() throws InterruptedException;
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
4. Condition 方法與 Object 方法的聯(lián)系與區(qū)別
聯(lián)系 1:都有一組類似的方法.
- Object 對(duì)象監(jiān)視器: Object.wait()、Object.wait(long timeout)、Object.notify()、Object.notifyAll()。
- Condition 對(duì)象: Condition.await()、Condition.awaitNanos(long nanosTimeout)、Condition.signal()、Condition.signalAll()。
聯(lián)系 2:都需要和鎖進(jìn)行關(guān)聯(lián)。
- Object 對(duì)象監(jiān)視器: 需要進(jìn)入 synchronized 語句塊(進(jìn)入對(duì)象監(jiān)視器)才能調(diào)用對(duì)象監(jiān)視器的方法。
- Condition 對(duì)象: 需要和一個(gè) Lock 綁定。
區(qū)別:
- Condition 拓展的語義方法,如 awaitUninterruptibly () 等待時(shí)忽略中斷方法;
- 在使用方法時(shí),Object 對(duì)象監(jiān)視器是進(jìn)入 synchronized 語句塊(進(jìn)入對(duì)象監(jiān)視器)后調(diào)用 Object.wait ()。而 Condition 對(duì)象需要和一個(gè) Lock 綁定,并顯示的調(diào)用 lock () 獲取鎖,然后調(diào)用 Condition.await ();
- 從等待隊(duì)列數(shù)量看,Object 對(duì)象監(jiān)視器是 1 個(gè)。而 Condition 對(duì)象是多個(gè)??梢酝ㄟ^多次調(diào)用 lock.newCondition () 返回多個(gè)等待隊(duì)列。
5. Condition 對(duì)象的創(chuàng)建
Condition 對(duì)象是由 Lock 對(duì)象創(chuàng)建出來的 (Lock.newCondition),換句話說,Condition 是依賴 Lock 對(duì)象的。那么我們來看看如果創(chuàng)建 Condition 對(duì)象。
此處僅提供示例代碼,后續(xù)我們?cè)谶M(jìn)行方法講解時(shí),會(huì)有全部的代碼示例,但在學(xué)習(xí)使用方法之前,我們必須先學(xué)會(huì)如何創(chuàng)建。
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
6. Condition 方法介紹
等待機(jī)制方法簡(jiǎn)介:
- void await() throws InterruptedException:當(dāng)前線程進(jìn)入等待狀態(tài),直到被其它線程的喚醒繼續(xù)執(zhí)行或被中斷;
- void awaitUninterruptibly():當(dāng)前線程進(jìn)入等待狀態(tài),直到被其它線程被喚醒;
- long awaitNanos(long nanosTimeout) throws InterruptedException:當(dāng)前線程進(jìn)入等待狀態(tài),直到被其他線程喚醒或被中斷,或者指定的等待時(shí)間結(jié)束;nanosTimeout 為超時(shí)時(shí)間,返回值 = 超時(shí)時(shí)間 - 實(shí)際消耗時(shí)間;
- boolean await(long time, TimeUnit unit) throws InterruptedException:當(dāng)前線程進(jìn)入等待狀態(tài),直到被其他線程喚醒或被中斷,或者指定的等待時(shí)間結(jié)束;與上個(gè)方法區(qū)別:可以自己設(shè)置時(shí)間單位,未超時(shí)被喚醒返回 true,超時(shí)則返回 false;
- boolean awaitUntil(Date deadline) throws InterruptedException:當(dāng)前線程等待狀態(tài),直到被其他線程喚醒或被中斷,或者指定的截止時(shí)間結(jié)束,截止時(shí)間結(jié)束前被喚醒,返回 true,否則返回 false。
通知機(jī)制方法簡(jiǎn)介:
- void signal():?jiǎn)拘岩粋€(gè)線程;
- void signalAll():?jiǎn)拘阉芯€程。
7. ReentrantLock 與 Condition 實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者
非常熟悉的場(chǎng)景設(shè)計(jì),這是我們?cè)谥v解生產(chǎn)者與消費(fèi)者模型時(shí)使用的案例設(shè)計(jì),那么此處有細(xì)微的修改如下,請(qǐng)學(xué)習(xí)者進(jìn)行比照學(xué)習(xí),印象更加深刻。
場(chǎng)景修改:
- 創(chuàng)建一個(gè)工廠類 ProductFactory,該類包含兩個(gè)方法,produce 生產(chǎn)方法和 consume 消費(fèi)方法(未改變);
- 對(duì)于 produce 方法,當(dāng)沒有庫存或者庫存達(dá)到 10 時(shí),停止生產(chǎn)。為了更便于觀察結(jié)果,每生產(chǎn)一個(gè)產(chǎn)品,sleep 3000 毫秒(5000 變 3000,調(diào)用地址也改變了,具體看代碼);
- 對(duì)于 consume 方法,只要有庫存就進(jìn)行消費(fèi)。為了更便于觀察結(jié)果,每消費(fèi)一個(gè)產(chǎn)品,sleep 5000 毫秒(sleep 調(diào)用地址改變了,具體看代碼);
- 庫存使用 LinkedList 進(jìn)行實(shí)現(xiàn),此時(shí) LinkedList 即共享數(shù)據(jù)內(nèi)存(未改變);
- 創(chuàng)建一個(gè) Producer 生產(chǎn)者類,用于調(diào)用 ProductFactory 的 produce 方法。生產(chǎn)過程中,要對(duì)每個(gè)產(chǎn)品從 0 開始進(jìn)行編號(hào) (新增 sleep 3000ms);
- 創(chuàng)建一個(gè) Consumer 消費(fèi)者類,用于調(diào)用 ProductFactory 的 consume 方法 (新增 sleep 5000ms);
- 創(chuàng)建一個(gè)測(cè)試類,main 函數(shù)中創(chuàng)建 2 個(gè)生產(chǎn)者和 3 個(gè)消費(fèi)者,運(yùn)行程序進(jìn)行結(jié)果觀察(未改變)。
實(shí)例:
public class DemoTest {
public static void main(String[] args) {
ProductFactory productFactory = new ProductFactory();
new Thread(new Producer(productFactory),"1號(hào)生產(chǎn)者"). start();
new Thread(new Producer(productFactory),"2號(hào)生產(chǎn)者"). start();
new Thread(new Consumer(productFactory),"1號(hào)消費(fèi)者"). start();
new Thread(new Consumer(productFactory),"2號(hào)消費(fèi)者"). start();
new Thread(new Consumer(productFactory),"3號(hào)消費(fèi)者"). start();
}
}
class ProductFactory {
private LinkedList<String> products; //根據(jù)需求定義庫存,用 LinkedList 實(shí)現(xiàn)
private int capacity = 10; // 根據(jù)需求:定義最大庫存 10
private Lock lock = new ReentrantLock(false);
private Condition p = lock.newCondition();
private Condition c = lock.newCondition();
public ProductFactory() {
products = new LinkedList<String>();
}
// 根據(jù)需求:produce 方法創(chuàng)建
public void produce(String product) {
try {
lock.lock();
while (capacity == products.size()) { //根據(jù)需求:如果達(dá)到 10 庫存,停止生產(chǎn)
try {
System.out.println("警告:線程("+Thread.currentThread().getName() + ")準(zhǔn)備生產(chǎn)產(chǎn)品,但產(chǎn)品池已滿");
p.await(); // 庫存達(dá)到 10 ,生產(chǎn)線程進(jìn)入 wait 狀態(tài)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
products.add(product); //如果沒有到 10 庫存,進(jìn)行產(chǎn)品添加
System.out.println("線程("+Thread.currentThread().getName() + ")生產(chǎn)了一件產(chǎn)品:" + product+";當(dāng)前剩余商品"+products.size()+"個(gè)");
c.signalAll(); //生產(chǎn)了產(chǎn)品,通知消費(fèi)者線程從 wait 狀態(tài)喚醒,進(jìn)行消費(fèi)
} finally {
lock.unlock();
}
}
// 根據(jù)需求:consume 方法創(chuàng)建
public String consume() {
try {
lock.lock();
while (products.size()==0) { //根據(jù)需求:沒有庫存消費(fèi)者進(jìn)入wait狀態(tài)
try {
System.out.println("警告:線程("+Thread.currentThread().getName() + ")準(zhǔn)備消費(fèi)產(chǎn)品,但當(dāng)前沒有產(chǎn)品");
c.await(); //庫存為 0 ,無法消費(fèi),進(jìn)入 wait ,等待生產(chǎn)者線程喚醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String product = products.remove(0) ; //如果有庫存則消費(fèi),并移除消費(fèi)掉的產(chǎn)品
System.out.println("線程("+Thread.currentThread().getName() + ")消費(fèi)了一件產(chǎn)品:" + product+";當(dāng)前剩余商品"+products.size()+"個(gè)");
p.signalAll();// 通知生產(chǎn)者繼續(xù)生產(chǎn)
return product;
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
private ProductFactory productFactory; //關(guān)聯(lián)工廠類,調(diào)用 produce 方法
public Producer(ProductFactory productFactory) {
this.productFactory = productFactory;
}
public void run() {
int i = 0 ; // 根據(jù)需求,對(duì)產(chǎn)品進(jìn)行編號(hào)
while (true) {
productFactory.produce(String.valueOf(i)); //根據(jù)需求 ,調(diào)用 productFactory 的 produce 方法
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
}
class Consumer implements Runnable {
private ProductFactory productFactory;
public Consumer(ProductFactory productFactory) {
this.productFactory = productFactory;
}
public void run() {
while (true) {
productFactory.consume();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
結(jié)果驗(yàn)證:
線程(1號(hào)生產(chǎn)者)生產(chǎn)了一件產(chǎn)品:0;當(dāng)前剩余商品1個(gè)
線程(2號(hào)生產(chǎn)者)生產(chǎn)了一件產(chǎn)品:0;當(dāng)前剩余商品2個(gè)
線程(1號(hào)消費(fèi)者)消費(fèi)了一件產(chǎn)品:0;當(dāng)前剩余商品1個(gè)
線程(2號(hào)消費(fèi)者)消費(fèi)了一件產(chǎn)品:0;當(dāng)前剩余商品0個(gè)
警告:線程(3號(hào)消費(fèi)者)準(zhǔn)備消費(fèi)產(chǎn)品,但當(dāng)前沒有產(chǎn)品
線程(2號(hào)生產(chǎn)者)生產(chǎn)了一件產(chǎn)品:1;當(dāng)前剩余商品1個(gè)
線程(1號(hào)生產(chǎn)者)生產(chǎn)了一件產(chǎn)品:1;當(dāng)前剩余商品2個(gè)
線程(3號(hào)消費(fèi)者)消費(fèi)了一件產(chǎn)品:1;當(dāng)前剩余商品1個(gè)
線程(2號(hào)消費(fèi)者)消費(fèi)了一件產(chǎn)品:1;當(dāng)前剩余商品0個(gè)
警告:線程(1號(hào)消費(fèi)者)準(zhǔn)備消費(fèi)產(chǎn)品,但當(dāng)前沒有產(chǎn)品
8. 小結(jié)
本節(jié)內(nèi)容為主要對(duì) Condition 接口進(jìn)行了講解,Condition 接口作為 Lock 接口的監(jiān)視器,是非常重要的接口,我們需要非常重視 Condition 接口的學(xué)習(xí)。
本節(jié)內(nèi)容最終的目的是使用 Condition 接口和 Lock 配合實(shí)現(xiàn)案例,核心內(nèi)容即為 Condition 接口的使用,請(qǐng)翻看生產(chǎn)者與消費(fèi)者一節(jié),對(duì)比進(jìn)行學(xué)習(xí)。