先看一個程序題目:一個程序產生16個日誌對象,並且需要運行16秒才能打印完這些日誌,請在程序中增加4個線程去調用parseLog方法來分頭打印這16個日誌對象,只需要在4秒內打印完這些日誌對象。
利用阻塞隊列的ArrayBlockingQueue來實現如下
public static void main(String[] args) {
final BlockingQueue<String> queue= new ArrayBlockingQueue<String>(16);
for(int i= 0;i<4;i++){
new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
//2、將日誌取出並調用打印日誌的方法
String log= queue.take();
parseLog(log);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
}
System.out.println( "begin:" +(System.currentTimeMillis()/1000));
//1、將日誌加入到阻塞隊列
for(int i =0; i<16; i++){
final String log= "" +(i+1);
{
try {
System.out.println("第"+(i+1) +"個日誌加入隊列"+ (System.currentTimeMillis()/1000));
queue.put(log);//給隊列加入元素
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void parseLog(String log){
System.out.println("輸出日誌"+log +"的時間" +(System.currentTimeMillis()/1000));
try {
//1秒鐘的時間佔用。
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
實現效果
另外 BlockingQueue成員詳細介紹
1. ArrayBlockingQueue 基於數組的阻塞隊列實現,在ArrayBlockingQueue內部,維護了一個定長數組,以便緩存隊列中的數據對象,這是一個常用的阻塞隊列,除了一個定長數組外,ArrayBlockingQueue內部還保存着兩個整形變量,分別標識着隊列的頭部和尾部在數組中的位置。 ArrayBlockingQueue在生產者放入數據和消費者獲取數據,都是共用同一個鎖對象,由此也意味着兩者無法真正並行運行,這點尤其不同於LinkedBlockingQueue;按照實現原理來分析,ArrayBlockingQueue完全可以採用分離鎖,從而實現生產者和消費者操作的完全並行運行。Doug
Lea之所以沒這樣去做,也許是因爲ArrayBlockingQueue的數據寫入和獲取操作已經足夠輕巧,以至於引入獨立的鎖機制,除了給代碼帶來額外的複雜性外,其在性能上完全佔不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue間還有一個明顯的不同之處在於,前者在插入或刪除元素時不會產生或銷燬任何額外的對象實例,而後者則會生成一個額外的Node對象。這在長時間內需要高效併發地處理大批量數據的系統中,其對於GC的影響還是存在一定的區別。而在創建ArrayBlockingQueue時,我們還可以控制對象的內部鎖是否採用公平鎖,默認採用非公平鎖。
2. LinkedBlockingQueue 基於鏈表的阻塞隊列,同ArrayListBlockingQueue類似,其內部也維持着一個數據緩衝隊列(該隊列由一個鏈表構成),當生產者往隊列中放入一個數據時,隊列會從生產者手中獲取數據,並緩存在隊列內部,而生產者立即返回;只有當隊列緩衝區達到最大值緩存容量時(LinkedBlockingQueue可以通過構造函數指定該值),纔會阻塞生產者隊列,直到消費者從隊列中消費掉一份數據,生產者線程會被喚醒,反之對於消費者這端的處理也基於同樣的原理。而LinkedBlockingQueue之所以能夠高效的處理併發數據,還因爲其對於生產者端和消費者端分別採用了獨立的鎖來控制數據同步,這也意味着在高併發的情況下生產者和消費者可以並行地操作隊列中的數據,以此來提高整個隊列的併發性能。作爲開發者,我們需要注意的是,如果構造一個LinkedBlockingQueue對象,而沒有指定其容量大小,LinkedBlockingQueue會默認一個類似無限大小的容量(Integer.MAX_VALUE),這樣的話,如果生產者的速度一旦大於消費者的速度,也許還沒有等到隊列滿阻塞產生,系統內存就有可能已被消耗殆盡了。ArrayBlockingQueue和LinkedBlockingQueue是兩個最普通也是最常用的阻塞隊列,一般情況下,在處理多線程間的生產者消費者問題,使用這兩個類足以。
3.PriorityBlockingQueue:類似於LinkedBlockQueue,但其所含對象的排序不是FIFO,而是依據對象的自然排序順序或者是構造函數的Comparator決定的順序.
4.SynchronousQueue:特殊的BlockingQueue,對其的操作必須是放和取交替完成的.
總結:可以用阻塞隊列實現同步。