併發——警戒塊(Guarded Blocks)(Java tutorial 翻譯)

5 警戒塊(Guarded Blocks)

線程間必須協調它們的動作。最普通的協調方式是警戒塊(Guarded Blocks)。這樣的塊就是,首先不斷查詢一個條件,直到這個條件變成“真”,才執行這個語句塊。爲了要正確的執行它們,我們要遵循一些步驟。

 

假設,guardedJoy是一個方法,它直到一個共享變量joy被其它線程設置爲“真”時才繼續執行。這個方法可以用一個循環,直到條件爲真退出循環,但這個循環很浪費,因爲通過不斷的執行來等待。如下:

 

public void guardedJoy() {
    // Simple loop guard. Wastes
    // processor time. Don't do this!
    while(!joy) {}
    System.out.println("Joy has been achieved!");
}

 

 

一個更有效的方法是調用Object.wait()來掛起當前線程。當另一個線程發出了一個通知,wait方法調用才能返回。

 

public synchronized guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

 

 

 注意:總是在一個測試等待條件的循環體內調用wait。不要假設中斷就是我們要等待的特定條件,或者條件一直保持爲“真”

 

象其它掛起執行的方法一樣,wait可以拋出InterruptedException。在這個例子中,我們只有忽略這個異常就行——我們只關心joy的值。

 

爲什麼這個版本guardedJoy方法需要同步呢?假使d是我在其上調用wait方法的對象。當一個線程調用d.wait(),它必須擁有d的內置鎖——否則將拋出一個錯誤。在一個同步方法裏調用wait方法是一個獲取內置鎖的簡單方法。

 

當調用wait方法時,線程釋放鎖並掛起執行。過一會兒後,另一個線程將獲取鎖並調用Object.notifyAll,通知在這個鎖上等待的所有線程有重要事件發生了:

public synchronized notifyJoy() {
    joy = true;
    notifyAll();

}

 

在第二個線程釋放鎖後的某個時候,第一個線程獲取鎖,從wait的返回處繼續執行。

 

       注意:有第二個通知方法,notify,它只喚醒一個線程。因爲notify並不允許你指定要喚醒的線程,這在大規模併發程序中很有用——也就是,存在大量線程的程序,它們都執行相似的任務。在這樣的應用程序中,你不用關心哪個線程先喚醒。

 

讓我們使用警戒塊來創建一個生產者/消費者的應用程序。這個應用程序在兩個線程間共享數據:生產者,生產數據,消費者,消費數據。兩個線程通過共享數據通信。這就需要協調:消費者線程在生產者沒有傳遞數據前是不能獲取數據的,生產者在消費者沒有消費掉舊數據前不能發送新數據。

 

這個例子中,數據就是一系列的文本消息,通過類型爲Drop的對象共享:

 

public class Drop {
    // Message sent from producer
    // to consumer.
    private String message;
    // True if consumer should wait
    // for producer to send message,
    // false if producer should wait for
    // consumer to retrieve message.
    private boolean empty = true;
 
    public synchronized String take() {
        // Wait until message is
        // available.
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = true;
        // Notify producer that
        // status has changed.
        notifyAll();
        return message;
    }
 
    public synchronized void put(String message) {
        // Wait until message has
        // been retrieved.
        while (!empty) {
            try { 
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = false;
        // Store message.
        this.message = message;
        // Notify consumer that status
        // has changed.
        notifyAll();
    }

}

 

消費者線程,定義在Producer中,發送一系列我們熟悉的消息。字符串“Done”表明所有消息都發送完了。爲了模擬真實世界應用程序的不確定性,生產者在兩個消息間會暫停一個隨機時間。

import java.util.Random;
 
public class Producer implements Runnable {
    private Drop drop;
 
    public Producer(Drop drop) {
        this.drop = drop;
    }
 
    public void run() {
        String importantInfo[] = {
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };
        Random random = new Random();
 
        for (int i = 0;
             i < importantInfo.length;
             i++) {
            drop.put(importantInfo[i]);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
        drop.put("DONE");
    }
}

 

 

消費者線程,定義爲Consumer,簡單的獲取消息並將它們打印出來,直到獲取“DONE”字符串爲止。這個線程也會暫停一個隨機時間。

import java.util.Random;
 
public class Consumer implements Runnable {
    private Drop drop;
 
    public Consumer(Drop drop) {
        this.drop = drop;
    }
 
    public void run() {
        Random random = new Random();
        for (String message = drop.take();
             ! message.equals("DONE");
             message = drop.take()) {
            System.out.format("MESSAGE RECEIVED: %s%n", message);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
    }
}

 

 

最後是main方法,定義爲ProducerConsumerExample類,它啓動produce和comsumer線程。

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Drop drop = new Drop();
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

 

 

注意:Drop類是用來演示警戒塊的,爲了避免重發明輪子,在編寫自己的共享對象時,請查看一下Java集合框架

發佈了11 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章