Oracle官方並發教程之Guarded Blocks

杜老師說 2022-01-07 14:44:23 阅读数:127

oracle 官方 教程 程之 guarded

原文連接譯文連接,譯者: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方法時,它釋放鎖並掛起。然後另一個線程請求並獲得這個鎖並調用Object.notifyAll通知所有等待該鎖的線程。

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

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類是用來演示Guarded Blocks如何工作的。為了避免重新發明輪子,當你嘗試創建自己的共享數據對象時,請查看Java Collections Framework中已有的數據結構。如需更多信息,請參考Questions and Exercises

原創文章,轉載請注明: 轉載自並發編程網 – ifeve.com本文鏈接地址: Oracle官方並發教程之Guarded Blocks

FavoriteLoading添加本文到我的收藏
版权声明:本文为[杜老師說]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201071444231037.html