多條線程之間有時(shí)需要數(shù)據(jù)交互,下面介紹5種線程間數(shù)據(jù)交互的方式,他們的使用處景各有不同。
PS:關(guān)于volatile的詳細(xì)介紹請(qǐng)移步至:Java并發(fā)編程的藝術(shù)(3)——volatile
這兩種方式都采取了同步機(jī)制實(shí)現(xiàn)多條線程間的數(shù)據(jù)通訊。與其說是“通訊”,倒不如說是“同享變量”來的恰當(dāng)。當(dāng)1個(gè)同享變量被volatile修飾 或 被同步塊包裹后,他們的讀寫操作都會(huì)直接操作同享內(nèi)存,從而各個(gè)線程都能看到同享變量最新的值,也就是實(shí)現(xiàn)了內(nèi)存的可見性。
這類方式能“傳遞”變量。當(dāng)需要傳遞1些公用的變量時(shí)就能夠使用這類方式。如:傳遞boolean flag,用于表示狀態(tài)、傳遞1個(gè)存儲(chǔ)所有任務(wù)的隊(duì)列等。
用這類方式實(shí)現(xiàn)線程的開關(guān)控制。
// 用于控制線程當(dāng)前的履行狀態(tài) private volatile boolean running = false; // 開啟1條線程 Thread thread = new Thread(new Runnable(){ void run(){ // 開關(guān) while(!running){
Thread.sleep(1000);
} // 履行線程任務(wù) doSometing();
}
}).start(); // 開始履行 public void start(){
running = true;
}
等待/通知機(jī)制的實(shí)現(xiàn)由Java完成,我們只需調(diào)用Object類的幾個(gè)方法便可。
// 同享的狀態(tài)變量 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未加同步。當(dāng)thread1履行到while那行后,判斷其狀態(tài)為true,此時(shí)若產(chǎn)生上下文切換,線程2開始履行,并1口氣履行完了;此時(shí)flag已是true,但是thread1繼續(xù)履行,遇到wait后便進(jìn)入等待態(tài);但此時(shí)已沒有線程能喚醒它了,因此就1直等待下去。
為何notify需要加鎖?且必須和wait使用同1把鎖?
首先,加鎖是為了保證同享變量的內(nèi)存可見性,讓它產(chǎn)生修改后能直接寫入同享內(nèi)存,好讓wait所處的線程立即看見。
其次,和wait使用同1把鎖是為了確保wait、notify之間的互斥,即:同1時(shí)刻,只能有其中1條線程履行。
為何必須使用同步塊的鎖對(duì)象調(diào)用wait函數(shù)?
首先,由于wait會(huì)釋放鎖,因此通過鎖對(duì)象調(diào)用wait就是告知wait釋放哪一個(gè)鎖。
其次,告知線程,你是在哪一個(gè)鎖對(duì)象上等待的,只有當(dāng)該鎖對(duì)象調(diào)用notify時(shí)你才能被喚醒。
為何必須使用同步塊的鎖對(duì)象調(diào)用notify函數(shù)?
告知notify,只喚醒在該鎖對(duì)象上等待的線程。
等待/通知機(jī)制用于實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者模式。
synchronized(鎖A){
flag = true;// 或:list.add(xx); 鎖A.notify();
}
synchronized(鎖A){ // 不滿足條件 while(!flag){ // 或:list.isEmpty() 鎖A.wait();
} // doSometing…… }
在之前的生產(chǎn)者-消費(fèi)者模式中,如果生產(chǎn)者沒有發(fā)出通知,那末消費(fèi)者將永久等待下去。為了不這類情況,我們可以給消費(fèi)者增加超時(shí)等待功能。該功能依托于wait(long)方法,只需在wait前的檢查條件中增加超時(shí)標(biāo)識(shí)位,實(shí)現(xiàn)以下:
public void get(long mills){
synchronized( list ){ // 不加超時(shí)功能 if ( mills <= 0 ) { while( list.isEmpty() ){ list.wait();
}
} // 添加超時(shí)功能 else {
boolean isTimeout = false; while(list.isEmpty() && isTimeout){ list.wait(mills);
isTimeout = true;
} // doSometing…… }
}
}
管道流用于在兩個(gè)線程之間進(jìn)行字節(jié)流或字符流的傳遞。
步驟以下:
1. 在1條線程中分別創(chuàng)建輸入流和輸出流;
2. 將輸入流和輸出留連接起來;
3. 將輸入流和輸出流分外傳遞給兩條線程;
4. 調(diào)用read和write方法就能夠?qū)崿F(xiàn)線程間通訊。
// 創(chuàng)建輸入流與輸出流對(duì)象 PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader(); // 連接輸入輸出流 out.connect(in); // 創(chuàng)建寫線程 class WriteThread extends Thread{ private PipedWriter out; public WriteThread(PipedWriter out){ this.out = out;
} public void run(){
out.write("hello concurrent world!");
}
} // 創(chuàng)建讀線程 class ReaderThread extends Thread{ private PipedReader in; public ReaderThread(PipedReader in){ this.in = in;
} public void run(){
in.read();
}
} //
public static void main(String[] args){ // 開啟1條線程 Thread t = new Thread(new Runnable(){ public void run(){ // doSometing }
}).start(); // 調(diào)用join,等待t線程履行終了 try{
t.join();
}catch(InterruptedException e){ // 中斷處理…… }
}
上一篇 (1)版本控制工具之Git
下一篇 Linux sed編輯器