1. 程式人生 > >基本執行緒同步(四)在同步程式碼中使用條件

基本執行緒同步(四)在同步程式碼中使用條件

宣告:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     譯者:許巧輝 校對:方騰飛

在同步程式碼中使用條件

在併發程式設計中的一個經典問題是生產者與消費者問題,我們有一個數據緩衝區,一個或多個數據的生產者在緩衝區儲存資料,而一個或多個數據的消費者,把資料從緩衝區取出。

由於緩衝區是一個共享的資料結構,我們必須採用同步機制,比如synchronized關鍵字來控制對它的訪問。但是我們有更多的限制因素,如果緩衝區是滿的,生產者不能儲存資料,如果緩衝區是空的,消費者不能取出資料。

對於這些型別的情況,Java在Object物件中提供wait(),notify(),和notifyAll() 方法的實現。一個執行緒可以在synchronized程式碼塊中呼叫wait()方法。如果在synchronized程式碼塊外部呼叫wait()方法,JVM會丟擲IllegalMonitorStateException異常。當執行緒呼叫wait()方法,JVM讓這個執行緒睡眠,並且釋放控制 synchronized程式碼塊的物件,這樣,雖然它正在執行但允許其他執行緒執行由該物件保護的其他synchronized程式碼塊。為了喚醒執行緒,你必 須在由相同物件保護的synchronized程式碼塊中呼叫notify()或notifyAll()方法。

在這個指南中,你將學習如何通過使用synchronized關鍵字和wait()和notify(),notifyAll()方法實現生產者消費者問題。

準備工作

這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。

如何做…

按以下步驟來實現的這個例子:

1.建立EventStorage類,包括一個名為maxSize,型別為int的屬性和一個名為storage,型別為LinkedList<Date>的屬性。

public class EventStorage {
private int maxSize;
private List<Date> storage;

2.實現這個類的構造器,初始化所有屬性。

public EventStorage(){
maxSize=10;
storage=new LinkedList<>();
}

3. 實現synchronized方法set(),用來在storage上儲存一個事件。首先,檢查storage是否已滿。如果滿了,呼叫wait()方 法,直到storage有空的空間。在方法的尾部,我們呼叫notifyAll()方法來喚醒,所有在wait()方法上睡眠的執行緒。

public synchronized void set(){
while (storage.size()==maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.offer(new Date());
System.out.printf("Set: %d",storage.size());
notifyAll();
}

4. 實現synchronized方法get(),用來在storage上獲取一個事件。首先,檢查storage是否有事件。如果沒有,呼叫wait()方 法直到,storage有一些事件,在方法的尾部,我們呼叫notifyAll()方法來喚醒,所有在wait()方法上睡眠的執行緒。

public synchronized void get(){
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Get: %d: %s",storage.
size(),((LinkedList<?>)storage).poll());
notifyAll();
}

5.建立Producer類,並指定它實現Runnable介面,它將實現這個示例的生產者。

public class Producer implements Runnable {

6.宣告一個EventStore物件,並實現(Producer類)構造器,初始化該物件。

private EventStorage storage;
public Producer(EventStorage storage){
this.storage=storage;
}

7.實現run()方法,該方法呼叫EventStorage物件的set()方法100次。

@Override
public void run() {
for (int i=0; i<100; i++){
storage.set();
}
}

8.建立Consumer類,並指定它實現Runnable介面,它將實現這個示例的消費者。

public class Consumer implements Runnable {

9.宣告一個EventStore物件,並實現(Consumer類)構造器,初始化該物件。

private EventStorage storage;
public Consumer(EventStorage storage){
this.storage=storage;
}

10.實現run()方法,該方法呼叫EventStorage物件的get()方法100次。

@Override
public void run() {
for (int i=0; i<100; i++){
storage.get();
}
}

11.通過建立類名為Main,且包括main()方法來實現這個示例的主類。

public class Main {
public static void main(String[] args) {

12.建立一個EventStorage物件。

EventStorage storage=new EventStorage();

13.建立一個Producer物件,並且用執行緒執行它。

Producer producer=new Producer(storage);
Thread thread1=new Thread(producer);

14.建立一個Consumer物件,並且用執行緒執行它。

Consumer consumer=new Consumer(storage);
Thread thread2=new Thread(consumer);

15.啟動這兩個執行緒。

thread2.start();
thread1.start();

它是如何工作的…

EventStorage 類的set()方法和get()方法是這個示例的關鍵。首先,set()方法檢查storage屬性是否有空閒空間。如果它滿了,呼叫wait()方法等 待有空閒的空間。當其他執行緒呼叫notifyAll()方法,這個執行緒將被喚醒並且再次檢查這個條件。這個notifyAll()方法並不保證執行緒會醒 來。這個過程是重複,直到storage有空閒空間,然後它可以生成一個新的事件並存儲它。

get()方法的行為是相似的。首先,它檢查storage是否有事件。如果EventStorage類是空的,呼叫wait()方法等待事件。當其他執行緒呼叫notifyAll()方法,這個執行緒將被喚醒並且再次檢查這個條件直到storage有一些事件。

註釋:在while迴圈中,你必須保持檢查條件和呼叫wait()方法。你不能繼續執行,直到這個條件為true。

不止這些…

synchronize關鍵字還有其他重要用法,請見其他指南中解釋這個關鍵字使用的參見部分。

參見

在第2章,基本執行緒同步中在同步類中安排獨立屬性的食譜。