基本執行緒同步(四)在同步程式碼中使用條件
宣告:本文是《 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章,基本執行緒同步中在同步類中安排獨立屬性的食譜。