Java™ 教程(守護阻塞)

守護阻塞

線程一般必須協調他們的操做,最多見的協調用法是守護阻塞,這樣的阻塞首先輪詢一個條件,該條件必須爲真,而後阻塞才能繼續,要正確執行此操做,須要執行許多步驟。html

例如,假設guardedJoy是一個方法,在另外一個線程設置了共享變量joy以前,該方法不能繼續,理論上,這種方法能夠簡單地循環直到知足條件,但該循環是浪費的,由於它在等待時持續執行。java

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的調用不會返回 — 儘管不必定是這個線程正在等待的事件:git

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,不要假設中斷是針對你正在等待的特定條件,或者條件仍然是 true

像許多暫停執行的方法同樣,wait會拋出InterruptedException,在這個例子中,咱們能夠忽略該異常 — 咱們只關心joy的值。github

爲何這個版本的guardedJoy是同步的?假設d是咱們用來調用wait的對象,當一個線程調用d.wait時,它必須擁有d的固有鎖 — 不然拋出一個錯誤,在同步方法中調用wait是獲取固有鎖的一種簡單方法。segmentfault

當調用wait時,線程釋放鎖並暫停執行,在未來的某個時間,另外一個線程將獲取相同的鎖並調用Object.notifyAll,通知等待該鎖的全部線程發生了重要的事情:api

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

在第二個線程釋放鎖以後的一段時間,第一個線程從新獲取鎖並經過從調用wait的返回來恢復。數據結構

還有第二種通知方法 notify,它喚醒單個線程,由於 notify不容許你指定被喚醒的線程,因此它僅在大規模並行應用程序中有用 — 也就是說,具備大量線程的程序,都作相似的事,在這樣的應用程序中,你不關心哪一個線程被喚醒。

讓咱們使用守護阻塞來建立生產者—消費者應用程序,這種應用程序在兩個線程之間共享數據:建立數據的生產者和使用數據的消費者。兩個線程使用共享對象進行通訊,協調相當重要:消費者線程在生產者線程交付以前不得嘗試檢索數據,若是消費者未檢索到舊數據,則生產者線程不得嘗試傳遞新數據。併發

在此示例中,數據是一系列文本消息,經過Drop類型的對象共享:oracle

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) {}
        }
    }
}

最後,這是在ProducerConsumerExample中定義的主線程,它啓動生產者和消費者線程。

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集合框架中的現有數據結構。

上一篇:併發活性

下一篇:不可變對象

相關文章
相關標籤/搜索