1. 程式人生 > 其它 >多條執行緒之間資料互動

多條執行緒之間資料互動

下面介紹5種執行緒間資料互動的方式,他們的使用處景各有不同。

1. volatile、synchronized關鍵字
PS:關於volatile的詳細介紹請移步至:Java併發程式設計的藝術(3)——volatile

1.1 如何實現通訊?
這兩種方式都採取了同步機制實現多條執行緒間的資料通訊。與其說是“通訊”,倒不如說是“同享變數”來的恰當。當1個同享變數被volatile修飾 或 被同步塊包裹後,他們的讀寫操作都會直接操作同享記憶體,從而各個執行緒都能看到同享變數最新的值,也就是實現了記憶體的可見性。

1.2 特點
這類方式本質上是“同享資料”,而非“傳遞資料”;只是從結果來看,資料好像是從寫執行緒傳遞到了讀執行緒;
這類通訊方式沒法指定特定的接收執行緒。當資料被修改後究竟哪條執行緒最早訪問到,這由作業系統隨機決定。
總的來講,這類方式其實不是真正意義上的“通訊”,而是“同享”。
1.3 使用處景
這類方式能“傳遞”變數。當需要傳遞1些公用的變數時就能夠使用這類方式。如:傳遞bo

olean flag,用於表示狀態、傳遞1個儲存所有任務的佇列等。

1.4 例子
用這類方式實現執行緒的開關控制。

// 用於控制執行緒當前的履行狀態 private volatile boolean running = false; // 開啟1條執行緒 Thread thread = new Thread(new Runnable(){ void run(){ // 開關 while(!running){ Thread.sleep(1000); } // 履行執行緒任務 doSometing(); } }).start(); // 開始履行 public void start(){ running = true; }
2. 等待/通知機制
2.1 如何實現?
等待/通知機制的實現由Java完成,我們只需呼叫Object類的幾個方法便可。

wait():將當前執行緒的狀態改成“等待態”,加入等待佇列,釋放鎖;直到當前執行緒產生中斷或呼叫了notify方法,這條執行緒才會被從等待佇列轉移到同步佇列,此時可以開始競爭鎖。
wait(long):和wait()功能1樣,只不過量了個超時動作。1旦超時,就會繼續履行wait以後的程式碼,它不會拋超時異常!
notify():將等待佇列中的1條執行緒轉移到同步佇列中去。
notifyAll():將等待佇列中的所有執行緒都轉移到同步佇列中去。
2.2 注意點
以上方法都必須放在同步塊中;
並且以上方法都只能由所處同步塊的鎖物件呼叫;
鎖物件A.notify()/notifyAll()只能喚醒由鎖物件A w

ait的執行緒;
呼叫notify/notifyAll函式後僅僅是將執行緒從等待佇列轉移到阻塞佇列,只有當該執行緒競爭到鎖後,才能從wait方法中返回,繼續履行接下來的程式碼;
2.3 QA
為何wait必須放在同步塊中呼叫?
由於等待/通知機制需要和同享狀態變數配合使用,1般是先檢查狀態,若狀態true則履行wait,即包括“先檢查履行”,因此需要把這1程序加鎖,確保其原子履行。
舉個例子:
// 同享的狀態變數 boolean flag = false; // 執行緒1 Thread t1 = new Thread(new Runnable(){ public void run(){ while(!flag){ wait(); } } }).start(); // 執行緒2 Thread t2 = new Thread(new Runnable(){ public void run(){ flag = true; notifyAll(); } }).start();
上述例子thread1未加同步。當thread1履行到while那行後,判斷其狀態為true,此時若產生上下文切換,執行緒2開始履行,並1口氣履行完了;此時flag已是true,但是thread1繼續履行,遇到wait後便進入等待態;但此時已沒有執行緒能喚醒它了,因此就1直等待下去。

為何notify需要加鎖?且必須和wait使用同1把鎖?
首先,加鎖是為了保證同享變數的記憶體可見性,讓它產生修改後能直接寫入同享記憶體,好讓wait所處的執行緒立即看見。
其次,和wait使用同1把鎖是為了確保wait、notify之間的互斥,即:同1時刻,只能有其中1條執行緒履行。

為何必須使用同步塊的鎖物件呼叫wait函式?
首先,由於wait會釋放鎖,因此通過鎖物件呼叫wait就是告知wait釋放哪一個鎖。
其次,告知執行緒,你是在哪一個鎖物件上等待的,只有當該鎖物件呼叫notify時你才能被喚醒。

為何必須使用同步塊的鎖物件呼叫notify函式?
告知notify,只喚醒在該鎖物件上等待的執行緒。

2.4 程式碼實現
等待/通知機制用於實現生產者和消費者模式。

生產者
synchronized(鎖A){ flag = true;// 或:list.add(xx); 鎖A.notify(); }
消費者
synchronized(鎖A){ // 不滿足條件 while(!flag){ // 或:list.isEmpty() 鎖A.wait(); } // doSometing…… }
2.5 超時等待模式
在之前的生產者-消費者模式中,如果生產者沒有發出通知,那末消費者將永久等待下去。為了不這類情況,我們可以給消費者增加超時等待功能。該功能依託於wait(long)方法,只需在wait前的檢查條件中增加超時標識位,實現以下:

public void get(long mills){ synchronized( list ){ // 不加超時功能 if ( mills <= 0 ) { while( list.isEmpty() ){ list.wait(); } } // 新增超時功能 else { boolean isTimeout = false; while(list.isEmpty() && isTimeout){ list.wait(mills); isTimeout = true; } // doSometing…… } } }
3. 管道流
3.1 作用
管道流用於在兩個執行緒之間進行位元組流或字元流的傳遞。

3.2 特點
管道流的實現依託PipedOutputStream、PipedInputStream、PipedWriter、PipedReader。分別對應位元組流和字元流。
他們與IO流的區分是:IO流是在硬碟、記憶體、Socket之間活動,而管道流僅在記憶體中的兩條執行緒間活動。
3.3 實現
步驟以下:
1. 在1條執行緒中分別建立輸入流和輸出流;
2. 將輸入流和輸出留連線起來;
3. 將輸入流和輸出流分外傳遞給兩條執行緒;
4. 呼叫read和write方法就能夠實現執行緒間通訊。

// 建立輸入流與輸出流物件 PipedWriter out = new PipedWriter(); PipedReader in = new PipedReader(); // 連線輸入輸出流 out.connect(in); // 建立寫執行緒 class WriteThread extends Thread{ private PipedWriter out; public WriteThread(PipedWriter out){ this.out = out; } public void run(){ out.write("hello concurrent world!"); } } // 建立讀程 class ReaderThread extends Thread{ private PipedReader in; public ReaderThread(PipedReader in){ this.in = in; } public void run(){ in.read(); } } //
4. join
4.1 作用
join能將併發履行的多條執行緒序列履行;
join函式屬於Thread類,通過1個thread物件呼叫。當線上程B中履行threadA.join(),執行緒B將被阻塞(底層呼叫wait方法),等到threadA執行緒執行結束後才會返回join方法。
被等待的那條執行緒可能會履行很長時間,因此join函式會丟擲InterruptedException。當呼叫threadA.interrupt()後,join函式就會丟擲該異常。
4.2 實現
public static void main(String[] args){ // 開啟1條執行緒 Thread t = new Thread(new Runnable(){ public void run(){ // doSometing } }).start(); // 呼叫join,等待t執行緒履行終了 try{ t.join(); }catch(InterruptedException e){ // 中斷處理…… } }

各類量化機器人系統開發:17166570329