前些日子接到了一個面試電話。面試內容我印象非常深,怎樣模擬一個並發?當時我的回答盡管也能夠算是正確的,但自己感覺缺乏實際能夠操作的細節,僅僅有一個大概的描寫敘述。
當時我的回答是:“線程所有在同一節點wait,然后在某個節點notifyAll。”
面試官:“那你聽說過驚群效應嗎?”
我:“我沒有聽過這個名詞,但我知道瞬間喚醒全部的線程,會讓CPU負載瞬間加大。
”
面試官:“那你有什么改進的方式嗎?”
我:“採用堵塞技術。在某個節點將全部的線程堵塞,在利用條件。線程的個數達到一定數量的時候。打開堵塞。
”
面試官好像是比較愜意,結束了這個話題。
面試結束后,我回頭這個塊進行了思考。要怎樣進行堵塞呢?我首先有一個思路就是。利用AtoInteger計算線程數,再利用synchronize方法塊堵塞一個線程,依據AtoInteger的推斷,運行sleep。
代碼例如以下:
/** * Created with IntelliJ IDEA. * User: 菜鳥大明 * Date: 14-10-21 * Time: 下午4:34 * To change this template use File | Settings | File Templates. */ public class CountDownLatchTest1 implements Runnable{ final AtomicInteger number = new AtomicInteger(); volatile boolean bol = false; @Override public void run() { System.out.println(number.getAndIncrement()); synchronized (this) { try { if (!bol) { System.out.println(bol); bol = true; Thread.sleep(10000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("並發數量為" + number.intValue()); } } public static void main(String[] args) { ExecutorService pool = Executors. newCachedThreadPool(); CountDownLatchTest1 test = new CountDownLatchTest1(); for (int i=0;i<10;i++) { pool.execute(test); } } }結果為:
0 2 1 4 3 false 5 6 7 8 9 並發數量為10 並發數量為10 並發數量為10 並發數量為10 並發數量為10 並發數量為10 並發數量為10 並發數量為10 並發數量為10 並發數量為10
從結果上來看,應該是能夠解決這個問題,利用了同步鎖,volatile攻克了同一時候釋放的問題,難點就在於開關。
后來查找資料,找到了一個CountDownLatch的類。專門干這個的
CountDownLatch是一個同步輔助類,宛如倒計時計數器,創建對象時通過構造方法設置初始值,調用CountDownLatch對象的await()方法則處於等待狀態。調用countDown()方法就將計數器減1,當計數到達0時,則全部等待者或單個等待者開始運行。
構造方法參數指定了計數的次數
new CountDownLatch(1)
countDown方法。當前線程調用此方法,則計數減一
cdAnswer.countDown();
awaint方法,調用此方法會一直堵塞當前線程,直到計時器的值為0
cdOrder.await();
直接貼代碼,轉載的代碼
/** * * @author Administrator *該程序用來模擬發送命令與運行命令,主線程代表指揮官。新建3個線程代表戰士,戰士一直等待着指揮官下達命令, *若指揮官沒有下達命令,則戰士們都必須等待。運行結果:一旦命令下達,戰士們都去運行自己的任務。指揮官處於等待狀態,戰士們任務運行完成則報告給 *指揮官。指揮官則結束等待。 */ public class CountdownLatchTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); //創建一個線程池 final CountDownLatch cdOrder = new CountDownLatch(1);//指揮官的命令。設置為1,指揮官一下達命令。則cutDown,變為0,戰士們運行任務 final CountDownLatch cdAnswer = new CountDownLatch(3);//由於有三個戰士,所以初始值為3,每個戰士運行任務完成則cutDown一次,當三個都運行完成,變為0。則指揮官停止等待。 for(int i=0;i<3;i++){ Runnable runnable = new Runnable(){ public void run(){ try { System.out.println("線程" + Thread.currentThread().getName() + "正准備接受命令"); cdOrder.await(); //戰士們都處於等待命令狀態 System.out.println("線程" + Thread.currentThread().getName() + "已接受命令"); Thread.sleep((long)(Math.random()*10000)); System.out.println("線程" + Thread.currentThread().getName() + "回應命令處理結果"); } catch (Exception e) { e.printStackTrace(); } finally { cdAnswer.countDown(); //任務運行完成,返回給指揮官,cdAnswer減1。 } } }; service.execute(runnable);//為線程池加入任務 } try { Thread.sleep((long)(Math.random()*10000)); System.out.println("線程" + Thread.currentThread().getName() + "即將公布命令"); cdOrder.countDown(); //發送命令,cdOrder減1,處於等待的戰士們停止等待轉去運行任務。 System.out.println("線程" + Thread.currentThread().getName() + "已發送命令,正在等待結果"); cdAnswer.await(); //命令發送后指揮官處於等待狀態。一旦cdAnswer為0時停止等待繼續往下運行 System.out.println("線程" + Thread.currentThread().getName() + "已收到全部響應結果"); } catch (Exception e) { e.printStackTrace(); } finally { } service.shutdown(); //任務結束。停止線程池的全部線程 } }
線程pool-1-thread-2正准備接受命令 線程pool-1-thread-3正准備接受命令 線程pool-1-thread-1正准備接受命令 線程main即將公布命令 線程pool-1-thread-2已接受命令 線程pool-1-thread-3已接受命令 線程pool-1-thread-1已接受命令 線程main已發送命令,正在等待結果 線程pool-1-thread-2回應命令處理結果 線程pool-1-thread-1回應命令處理結果 線程pool-1-thread-3回應命令處理結果 線程main已收到全部響應結果
上述也是一種實現方式,用countDownLatch的await()方法,取代了synchronize 和 sleep的堵塞功能,通過countDown的方法來當做開關,和計算線程數量的一種方式。
差別的話,肯定是后者會好一些,由於第一種方式依靠sleep(xxx)來堵塞把握不好最短時間,太短了,可能來沒有達到固定線程數就會打開開關。
至於兩者性能上的差別,眼下我還不得而知,有機會測試一下。