1. 程式人生 > >Oracle官方併發教程之Guarded Blocks

Oracle官方併發教程之Guarded Blocks

原文連線譯文連線,譯者:Greester,校對:鄭旭東

多執行緒之間經常需要協同工作,最常見的方式是使用Guarded Blocks,它迴圈檢查一個條件(通常初始值為true),直到條件發生變化才跳出迴圈繼續執行。在使用Guarded Blocks時有以下幾個步驟需要注意:

假設guardedJoy()方法必須要等待另一執行緒為共享變數joy設值才能繼續執行。那麼理論上可以用一個簡單的條件迴圈來實現,但在等待過程中guardedJoy方法不停的檢查迴圈條件實際上是一種資源浪費。

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

更加高效的方法是呼叫Object.wait將當前執行緒掛起,直到有另一執行緒發起事件通知(儘管通知的事件不一定是當前執行緒等待的事件)。

public synchronized void 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的值,所以忽略了InterruptedException。

為什麼guardedJoy是synchronized方法?假設d是用來呼叫wait的物件,當一個執行緒呼叫d.wait,它必須要擁有d的內部鎖(否則會丟擲異常),獲得d的內部鎖的最簡單方法是在一個synchronized方法裡面呼叫wait。

當一個執行緒呼叫wait方法時,它釋放鎖並掛起。然後另一個執行緒請求並獲得這個鎖並呼叫

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

當第二個執行緒釋放這個該鎖後,第一個執行緒再次請求該鎖,從wait方法返回並繼續執行。

注意:還有另外一個通知方法,notify(),它只會喚醒一個執行緒。但由於它並不允許指定哪一個執行緒被喚醒,所以一般只在大規模併發應用(即系統有大量相似任務的執行緒)中使用。因為對於大規模併發應用,我們其實並不關心哪一個執行緒被喚醒。

現在我們使用Guarded blocks建立一個生產者/消費者應用。這類應用需要在兩個執行緒之間共享資料:生產者生產資料,消費者使用資料。兩個執行緒通過共享物件通訊。在這裡,執行緒協同工作的關鍵是:生產者釋出資料之前,消費者不能夠去讀取資料;消費者沒有讀取舊資料前,生產者不能釋出新資料。

在下面的例子中,資料通過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) {}
        }
    }
}
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類是用來演示Guarded Blocks如何工作的。為了避免重新發明輪子,當你嘗試建立自己的共享資料物件時,請檢視Java Collections Framework中已有的資料結構。如需更多資訊,請參考Questions and Exercises


[email protected]唯品會。關注Java語言、併發程式設計、Spring框架等。