如何模擬一個並發?當時我的回答雖然也可以算是正確的,但自己感覺缺乏實際可以操作的細節,只有一個大概的描述。
當時我的回答是:“線程全部在同一節點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)來阻塞把握不好最短時間,太短了,可能來沒有達到固定線程數就會打開開關。
至於兩者性能上的區別,目前我還不得而知,有機會測試一下。