標籤:next 並行 rand 傳遞資料 anti tar ++ util 處理
線程經常需要協調其動作,最常用的協調方法就是保護代碼塊,該代碼塊以一個條件判斷開始,當判斷為true時才能開始執行。
假設一個方法guradedJoy必須等到變數joy由其他線程設定過後才能開始執行,這樣的方法,理論上簡單的迴圈直到滿足條件就可以了,但是這樣的迴圈是比較浪費資源的,因為等待的時候是在不停的執行的。
public void guardedJoy() { // 簡單的循環保護,不建議這樣做,因為比較浪費處理器時間 while(!joy) {} System.out.println("Joy has been achieved!");}
一個更高效的方法就是調用Object類的wait方法來暫停當前的線程,wait方法的調用只有在另外一個線程發出特定的事件已經發生的通知時才會返回,但該特定的事件不一定是這個線程正在等待的事件:
public synchronized void guardedJoy() { // 該保護用法只對於特定的事件迴圈一次,可以不是正在等待的事件 while(!joy) { try { wait(); } catch (InterruptedException e) {} } System.out.println("Joy and efficiency have been achieved!");}
注意:在一個測試一個等待條件的迴圈中請使用wait方法,不要認為中斷是針對於你所等待的條件的,或者等待的條件依然為true。
像其他暫停執行的方法一樣,wait方法可以拋出InterruptException,當一個線程調用一個對象的wait方法時,該線程必須擁有該對象的內建鎖(如上例中guardedJoy方法為同步方法),否則將拋出錯誤,在同步方法中調用wait方法是一個請求內建鎖的簡單途徑。
當調用wait方法時,線程釋放鎖並暫停執行。在接下來的時間裡,另外一個線程將請求同一個鎖並調用Object類的notifyAll,通知等待該鎖的所有線程一些已經發生的重要事情。當第二個線程釋放了鎖之後,第一個線程就會請求該鎖並從調用的wait方法中返回。
注意:還有另外一個通知的方法,notify,該方法只喚醒一個線程,因為notify方法無法指定要被喚醒的線程,這個方法只在大規模並行程式中有用,也就是說,程式中有許多線程做相同的事情,這樣的程式中,不必在意喚醒的是哪個線程。
一下利用保護代碼塊來建立一個簡單的生產者-消費者程式,給程式在兩個線程之間分享資料,生產者產生資料,而消費者對資料進行處理,兩個線程之間通過一個共用的對象進行通訊。線程之間的協調是很重要的:消費者在生產者傳遞資料之間不能檢索資料,而生產者在就得資料被消費者檢索之前不能傳遞新的資料。
以下定義一個類用來建立通訊的對象:
public class Drop { // 生產者發給消費者的資訊 private String message; // 如果消費者需要等待消費者發送資訊則為true,如果需要等待消費者接受資訊則為false private boolean empty = true; public synchronized String take() { // 等待資訊可用 while (empty) { try { wait(); } catch (InterruptedException e) {} } // 轉換狀態 empty = true; // 通知生產者資訊已被檢索 notifyAll(); return message; } public synchronized void put(String message) { // 等待資訊被檢索 while (!empty) { try { wait(); } catch (InterruptedException e) {} } // 轉換狀態 empty = false; // Store message. this.message = message; // 通知消費者狀態已經改變 notifyAll(); }}
定義一個Producer類,發送一系列熟悉的資訊,字串“DONE”表明所有的資訊已經被發送,為類比真實的程式,發送資訊的間歇Producer類會停頓隨機的一段時間。
//匯入隨機類,以產生一個隨機數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的集合架構(Java Collections Framework)提供了資料分享的類,提供豐富的功能,因此可以不必定義自己的類。
java-並發-保護代碼塊