前些日子接到了一個面試電話。面試內容我印象很是深,怎樣模擬一個併發?當時個人回答儘管也可以算是正確的,但本身感受缺少實際可以操做的細節,僅僅有一個大概的描寫敘述。java
當時個人回答是:「線程全部在同一節點wait,而後在某個節點notifyAll。」面試
面試官:「那你據說過驚羣效應嗎?」併發
我:「我沒有聽過這個名詞,但我知道瞬間喚醒所有的線程,會讓CPU負載瞬間加大。dom
」ide
面試官:「那你有什麼改進的方式嗎?」post
我:「採用堵塞技術。在某個節點將所有的線程堵塞,在利用條件。線程的個數達到必定數量的時候。打開堵塞。性能
」this
面試官好像是比較愜意,結束了這個話題。spa
面試結束後,我回頭這個塊進行了思考。要怎樣進行堵塞呢?我首先有一個思路就是。利用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)來堵塞把握很差最短期,過短了,可能來沒有達到固定線程數就會打開開關。
至於二者性能上的差異,眼下我還不得而知,有機會測試一下。