wait()
、notify()
與 notifyAll()
是由 Object 所提供的方法,您在定義自己的類別時會繼承下來(記得 Java 中所有的物件最頂層都繼承自 Object),wait()
、notify()
與 notifyAll()
都被宣告為 "final
",所以您無法重新定義它們,透過這三個方法您可以控制執行緒是否為 Runnable 狀態。您必須在同步化的方法或區塊中呼叫
wait()
方法,當物件的 wait()
方法被調用,目前的執行緒會被放入物件的「等待集合」(Wait set)中, 執行緒會釋放物件的鎖定,其它的執行緒可以競爭鎖定,取得鎖定的執行緒可以執行同步化區塊;被放在等待集中的執行緒將不參與執行緒的排班,wait()
可 以指定等待的時間,如果指定時間的話,則時間到之後執行緒會再度加入排班,如果指定時間 0 或不指定,則執行緒會持續等待,直到有被中斷(interrupt)或是被告知(notify)可以參與排班。當物件的
notify()
被調用,它會從物件的等待集中選出「一個」執行緒加入排班,被選出的執行緒是隨機的,被選出的執行緒會與其它正在執行的執行緒共 同競爭對物件的鎖定;如果您呼叫 notifyAll()
,則「所有」在等待集中的執行緒都會被喚醒,這些執行緒會與其它正在執行的執行緒共同競爭對物件的 鎖定。簡單的說,當執行緒呼叫到物件的
wait()
方法時,表示它要先讓出物件的被同步區使用權並等待通知,或是等待一段指定的時間,直到被通知或時間到時再從 等待點開始執行,這就好比您要叫某人作事,作到一半時某人叫您等候通知(或等候1分鐘之類的),當您被通知(或時間到時)某人會繼續為您服務。說明
wait()
、notify()
或 notifyAll()
的應用最常見的一個例子,就是生產者(Producer)與消費者(Consumer)的 例子,如果生產者會將產品交給店員,而消費者從店員處取走產品,店員一次只能持有固定數量產品,如果生產者生產了過多的產品,店員叫生產者等一下 (wait),如果店中有空位放產品了再通知(notify)生產者繼續生產,如果店中沒有產品了,店員會告訴消費者等一下(wait),如果店中有產品 了再通知(notify)消費者來取走產品。以下舉一個最簡單的:生產者每次生產一個
int
整數交給在店員上,而消費者從店員處取走整數,店員一次只能持有一個整數。以程式實例來看,首先是生產者:
Producer.java
- package onlyfun.caterpillar;
- public class Producer implements Runnable {
- private Clerk clerk;
- public Producer(Clerk clerk) {
- this.clerk = clerk;
- }
- public void run() {
- System.out.println("生產者開始生產整數......");
- // 生產1到10的整數
- for(int product = 1; product <= 10; product++) {
- try {
- // 暫停隨機時間
- Thread.sleep((int) (Math.random() * 3000));
- }
- catch(InterruptedException e) {
- e.printStackTrace();
- }
- // 將產品交給店員
- clerk.setProduct(product);
- }
- }
- }
再來是消費者:
Consumer.java
- package onlyfun.caterpillar;
- public class Consumer implements Runnable {
- private Clerk clerk;
- public Consumer(Clerk clerk) {
- this.clerk = clerk;
- }
- public void run() {
- System.out.println("消費者開始消耗整數......");
- // 消耗10個整數
- for(int i = 1; i <= 10; i++) {
- try {
- // 等待隨機時間
- Thread.sleep((int) (Math.random() * 3000));
- }
- catch(InterruptedException e) {
- e.printStackTrace();
- }
- // 從店員處取走整數
- clerk.getProduct();
- }
- }
- }
生產者將產品放至店員,而消費者從店員處取走產品,所以店員來決定誰必須等待並等候通知。
Clerk.java
- package onlyfun.caterpillar;
- public class Clerk {
- // -1 表示目前沒有產品
- private int product = -1;
- // 這個方法由生產者呼叫
- public synchronized void setProduct(int product) {
- while(this.product != -1) {
- try {
- // 目前店員沒有空間收產品,請稍候!
- wait();
- }
- catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.product = product;
- System.out.printf("生產者設定 (%d)%n", this.product);
- // 通知等待區中的一個消費者可以繼續工作了
- notify();
- }
- // 這個方法由消費者呼叫
- public synchronized int getProduct() {
- while(this.product == -1) {
- try {
- // 缺貨了,請稍候!
- wait();
- }
- catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- int p = this.product;
- System.out.printf(
- "消費者取走 (%d)%n", this.product);
- this.product = -1;
- // 通知等待區中的一個生產者可以繼續工作了
- notify();
- return p;
- }
- }
根據 規格書中所說明 ,執行緒也有可能在未經
notify()
、interrupt()
或逾時的情況下自動甦醒(spurious wakeup),雖然這種情況實務上很少發生,但應用程式應考量這種情況,你必須持續檢測這種情況,因而 wait()
必須總是在迴圈中執行,例如:- synchronized (obj) {
- while (執行條件不成立時)
- obj.wait(timeout);
- ... // 執行一些動作進行判斷
- }
使用這麼一個程式來測試:
WaitNotifyDemo.java
- package onlyfun.caterpillar;
- public class WaitNotifyDemo {
- public static void main(String[] args) {
- Clerk clerk = new Clerk();
- Thread producerThread = new Thread(
- new Producer(clerk)
- );
- Thread consumerThread = new Thread(
- new Consumer(clerk)
- );
- producerThread.start();
- consumerThread.start();
- }
- }
執行結果:
- 生產者開始生產整數......
- 消費者開始消耗整數......
- 生產者設定 (1)
- 消費者取走 (1)
- 生產者設定 (2)
- 消費者取走 (2)
- 生產者設定 (3)
- 消費者取走 (3)
- 生產者設定 (4)
- 消費者取走 (4)
- 生產者設定 (5)
- 消費者取走 (5)
- 生產者設定 (6)
- 消費者取走 (6)
- 生產者設定 (7)
- 消費者取走 (7)
- 生產者設定 (8)
- 消費者取走 (8)
- 生產者設定 (9)
- 消費者取走 (9)
- 生產者設定 (10)
- 消費者取走 (10)
生產者會生產10個整數,而消費者會消耗10個整數,由於店員處只能放置一個整數,所以每生產一個就消耗一個,其結果如上所示是無誤的。
如果一個執行緒進入物件的等待集中,您可以中斷它的等待,這時將會發生InterruptedException例外物件,interrupt()方法可用來進行這項工作。
0 回應:
張貼留言