守護阻塞
線程通常必須協調他們的操作,最常見的協調用法是守護阻塞,這樣的阻塞首先輪詢一個條件,該條件必須為真,然后阻塞才能繼續,要正確執行此操作,需要執行許多步驟。
例如,假設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 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的值。
為什么這個版本的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) {} } } }
最后,這是在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集合框架中的現有數據結構。上一篇:并發活性 下一篇:不可變對象
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73055.html
并發活性 并發應用程序及時執行的能力被稱為其活性,本節描述了最常見的活性問題,死鎖,并繼續簡要描述其他兩個活性問題,饑餓和活鎖。 死鎖 死鎖描述了兩個或多個線程永遠被阻塞,等待彼此的情況,這是一個例子。 Alphonse和Gaston是朋友,是禮貌的忠實信徒,禮貌的一個嚴格規則是,當你向朋友鞠躬時,你必須一直鞠躬,直到你的朋友有機會還禮,不幸的是,這條規則沒有考慮到兩個朋友可能同時互相鞠躬的可能性...
摘要:多線程和并發問題是技術面試中面試官比較喜歡問的問題之一。線程可以被稱為輕量級進程。一個守護線程是在后臺執行并且不會阻止終止的線程。其他的線程狀態還有,和。上下文切換是多任務操作系統和多線程環境的基本特征。 多線程和并發問題是 Java 技術面試中面試官比較喜歡問的問題之一。在這里,從面試的角度列出了大部分重要的問題,但是你仍然應該牢固的掌握Java多線程基礎知識來對應日后碰到的問題。(...
摘要:線程可以被稱為輕量級進程。一個守護線程是在后臺執行并且不會阻止終止的線程。其他的線程狀態還有,和。上下文切換是多任務操作系統和多線程環境的基本特征。在的線程中并沒有可供任何對象使用的鎖和同步器。 原文:Java Multi-Threading and Concurrency Interview Questions with Answers 翻譯:并發編程網 - 鄭旭東 校對:方騰飛 多...
摘要:時間年月日星期六說明本文部分內容均來自慕課網。慕課網教學源碼無學習源碼第一章課前準備前言課程說明比較和這兩種線程創建的方式,需要知道和的基本創建方式。一旦主線程獲取到了用戶的輸入,這時候,阻塞就會解除掉,主線程繼續運行,直到結束。 時間:2017年07月08日星期六說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學源碼:無學習源碼:https://g...
摘要:死亡線程方法執行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。直到當前的線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。枚舉程序中的線程。強迫一個線程等待。通知一個線程繼續運行。 一. 線程狀態轉換圖 showImg(https://segmentfault.com/img/bV38ef?w=968&h=680); 線程間的狀態轉換說明: 新建(new)...
閱讀 3417·2021-11-24 09:39
閱讀 1806·2021-11-17 09:33
閱讀 3530·2021-10-12 10:12
閱讀 5037·2021-09-22 15:51
閱讀 1121·2019-08-30 13:11
閱讀 3580·2019-08-30 10:59
閱讀 574·2019-08-30 10:48
閱讀 1322·2019-08-26 13:48