高級JAVA - 多線程之CountDownLatch

package com.xbz.thread.juc.countDownLatch;

import java.util.concurrent.CountDownLatch;

/**
 * @title CountDownLatch類的應用
 *      CountDownLatch類位於java.util.concurrent(簡稱juc)包下 , 它是一個同步工具類 , 利用它可以實現類似計數器的功能
 *      比如有一個任務A , 它要等待其他4個任務結束執行之後才能執行 , 此時就可以利用CountDownLatch來實現這種功能了
 *      主要用來協調多個線程之間的同步 , 或者說起到線程之間的通信 ( 而不是用作互斥的作用 )
 *      CountDownLatch能夠使一個線程在等待另外一些線程完成各自工作之後 , 再繼續執行 . 使用一個計數器進行實現 .
 *      計數器初始值爲線程的數量 .
 *      當每一個線程完成自己任務後 , 計數器的值就會減一(手動) .
 *      當計數器的值爲0時 , 表示所有的線程都已經完成了任務 , 然後在CountDownLatch上等待的線程就可以恢復執行任務
 *
 * CountDownLatch類只提供了一個構造器 :
 *      public CountDownLatch(int count)//參數count爲計數值 , 即需要先執行幾個線程
 *
 * CountDownLatch類的主要方法 :
 *      void await()//調用await()方法的線程會被掛起 , 它會等待直到count值爲0才繼續執行
 *      boolean await(long timeout, TimeUnit unit)//和await()類似 , 只不過等待一定的時間後count值還沒變爲0的話也會繼續執行
 *      void countDown()//將count值減1

 * CountDownLatch的不足
 *      CountDownLatch是一次性的 , 計數器的值只能在構造方法中初始化一次 , 之後沒有任何機制再次對其設置值 , 當CountDownLatch使用完畢後 , 它不能再次被使用
 * @author Xingbz
 * @createDate 2018-7-18
 */
public class MainDemo {
    public static void main(String[] args) throws Exception {
//        demo1();
        demo2();
    }

    /**
     * @title CountDownLatch典型用法1
     * @description 某一線程在開始運行前等待n個線程結束執行 . 
     *      將CountDownLatch的計數器初始化爲n new CountDownLatch(n)  , 
     *      每當一個任務線程結束執行 , 就將計數器減1 countdownlatch.countDown() , 
     *      當計數器的值變爲0時 , 在CountDownLatch上 await() 的線程就會被喚醒 . 
     *      一個典型應用場景就是啓動一個服務時 , 主線程需要等待多個組件加載完畢 , 之後再繼續執行 . 
     * @author Xingbz
     */
    public static void demo1() throws Exception {
//        final CountDownLatch latch = new CountDownLatch(2);//2個線程後開始執行
        final CountDownLatch latch = new CountDownLatch(2) {//此處是爲了演示線程過程特意重寫了await方法, 實際應用如上聲明即可
            @Override
            public void await() throws InterruptedException {
                System.out.println("當前線程 : " + Thread.currentThread().getName() + " 進入阻塞狀態 . 當前計數器 : " + this.getCount());
                super.await();
            }
        };//2個線程後開始執行

        runThread("線程1", 2000L, latch);
        runThread("線程2", 3000L, latch);

        System.out.println("等待2個子線程結束執行...");
        latch.await();//當前線程掛起 , 直到計數器爲0
//      latch.await(20, TimeUnit.SECONDS);//最多阻塞20S , 20S後即使計數器不是0也繼續往下執行
        System.out.println("2個子線程已經結束執行");
        System.out.println("繼續執行主線程");
    }


    /**
     * @title CountDownLatch典型用法2
     * @description
     *      實現多個線程開始執行任務的最大並行性 . 
     *      注意是並行性 , 不是併發 , 強調的是多個線程在某一時刻同時開始執行 . 
     *      類似於賽跑 , 將多個線程放到起點 , 等待發令槍響 , 然後同時開跑 . 
     *      做法是額外初始化一個共享的CountDownLatch(1) , 將其計數器初始化爲1 , 
     *      多個線程在開始執行任務前首先 coundownlatch.await() , 當主線程調用 countDown() 時 , 計數器變爲0 , 多個線程同時被喚醒
     * @author Xingbz
     */
    public static void demo2() throws Exception {
        CountDownLatch countDown = new CountDownLatch(1);
        CountDownLatch latch = new CountDownLatch(5);

        for (int i = 0; i < 5; ++i) {// 依次創建並啓動處於等待狀態的5個線程
            runThread("線程" + (i + 1), 2000L, countDown, latch);
        }

        System.out.println("執行了5個線程並進入了阻塞狀態 . 等待統一啓動 . . . ");
        countDown.countDown();
        System.out.println("主控器結束計數 , 所有線程同時開始執行 . . .");
        latch.await();
        System.out.println("完成");
    }

    /**
     * @title 模擬啓動一個子線程工作
     * @param threadName 線程名稱
     * @param sleepTime 休眠時間
     * @param countDown 主控制器 CountDownLatch 負責所有線程啓動後先進入阻塞 "預備" 狀態
     * @param latch 輔控制器 CountDownLatch 負責所有線程執行完成後繼續運行主線程
     * @author Xingbz
     */
    private static void runThread(String threadName, Long sleepTime, CountDownLatch countDown, CountDownLatch latch) {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("子線程[" + Thread.currentThread().getName() + "]準備執行");
                countDown.await();
                Thread.sleep(sleepTime);
                System.out.println("子線程[" + Thread.currentThread().getName() + "]正在執行");
                Thread.sleep(sleepTime);
                latch.countDown();//子線程結束執行 , latch計數器減1
                System.out.println("子線程[" + Thread.currentThread().getName() + "]結束執行 , latch計數器-1 . 當前計數器 : " + latch.getCount());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, threadName);
        thread.start();
    }

    /**
     * @title 模擬啓動一個子線程工作
     * @param threadName 線程名稱
     * @param sleepTime 休眠時間
     * @param latch CountDownLatch
     * @author Xingbz
     */
    private static void runThread(String threadName, Long sleepTime, CountDownLatch latch) {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("子線程[" + Thread.currentThread().getName() + "]正在執行");
                Thread.sleep(sleepTime);
                latch.countDown();//子線程結束執行 , latch計數器減1
                System.out.println("子線程[" + Thread.currentThread().getName() + "]結束執行 , latch計數器-1 . 當前計數器 : " + latch.getCount());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, threadName);
        thread.start();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章