1. 程式人生 > >Java多執行緒高併發知識點二:執行緒池和CountDownLatch

Java多執行緒高併發知識點二:執行緒池和CountDownLatch

執行緒池

池化是在為了複用常用的一種技術解決思路,比如連線池、執行緒池等,執行緒池主要是為了降低執行緒建立和銷燬造成的資源消耗,進而起到解決系統資源,提高系統相應速度的目的。Java中的ThreadPoolExecutor是JDK為我們提供好的執行緒池工具。

ThreadPoolExecutor executor = new ThreadPoolExecutor(50,//核心池大小
                5000,//最大池大小
                200,//執行緒最大空閒時間,超過此空閒時間可以被收回
                TimeUnit.MILLISECONDS, //最大空閒時間的單位
new ArrayBlockingQueue<Runnable>(10)//用於儲存執行任務的佇列,10的意思是可以允許10個任務在排隊,如果佇列滿了,則建立新的執行緒。 );

向執行緒池中提交任務

Task myTask = new Task();
executor.execute(myTask);

//任務定義
class Task implements Runnable {
.....
}

executor.submit(myTask)也可以用於提交任務,但是主要用於帶返回值的Task,用的並不多,因此不做贅述。

在此篇博文中我們用CountDownLatch來記錄任務的完成情況,完成執行緒間的通訊。

CountDownLatch

使用了多執行緒處理任務後,最常見的需要就是主執行緒並不知道任務的完成情況,因此無法決定是繼續往下進行還是繼續完畢。CountDownLatch是JDK提供給我們的多執行緒間通訊的一個工具,用於讓主執行緒知道任務完成的進度。

舉個例子來說:

目前一名老師接到了一個任務,說要把教室裡的桌子都擦乾淨

流程是:

  1. 把桌子擦乾淨
  2. 等全部桌子乾淨了之後,去敲上課鈴。

如果不用多執行緒做的話,教室裡有1000張桌子,老師(主執行緒)要逐一把桌子擦乾淨,然後去敲上課鈴。效率比較低,於是老師叫來了50個同學(50個執行緒),告訴他們要擦桌子。但是問題是老師無法檢查每張桌子是否都擦了,所以不知道什麼時候去敲上課鈴,於是約定同學擦完一張桌子就要舉手報告老師一次,老師只需要記錄同學的舉手次數,等到同學的舉手次數到了1000次,老師去敲上課鈴。而這個CountDownLatch其實就是這個計數器

主執行緒的程式碼:

//宣告,1000的意思,意味著1000個任務等待完成
CountDownLatch n = new CountDownLatch(1000);

//將1000個任務交給執行緒池去處理
for(int i=0;i<1000;i++){
    //啟動一個任務
    Task myTask = new Task(i,n);
    executor.execute(myTask);
}

//等待所有執行緒完畢
try {
    n.await();
} catch (InterruptedException e) {
logger.error(e);
}
//去敲上課鈴

任務的程式碼:

public void run() {
    //做你的事情,擦桌子
    //....

    //事情幹完了
    n.countDown();
}

當然,除了CountDownLatch,多執行緒通訊中還存在CyclicBarrier和Semaphore等,但是CountDownLatch是其中最常用也最簡單的一種方式,其他的方式,大家自行搜尋理解即可。

高併發示例程式碼,

最後附上我經常用的做簡單的多執行緒測試時,模擬高併發時的程式碼,希望能節約大家一點時間。

Tester.java

package com.test;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.concurrent.*;

public class Tester {

    private static Logger logger = LogManager.getLogger(Tester.class);


    public void doSome(int num){
        //啟用執行緒池(最大不能超過500個)
        ThreadPoolExecutor executor = new ThreadPoolExecutor(50,//核心池大小
                5000,//最大池大小
                200,//執行緒最大空閒時間,超過此空閒時間可以被收回
                TimeUnit.MILLISECONDS, //最大空閒時間的單位
                new ArrayBlockingQueue<Runnable>(10)//用於儲存執行任務的佇列,可以選擇其他不同的佇列來做任務管理
        );



        CountDownLatch n = new CountDownLatch(num);

        for(int i=0;i<num;i++){
            //啟動一個任務
            Task myTask = new Task(i,n);
            executor.execute(myTask);
        }

        logger.info("全部執行的任務數量:"+executor.getTaskCount());
        logger.info("已完成的任務數量:"+executor.getCompletedTaskCount());
        logger.info("曾經建立過最大的執行緒數量:"+executor.getPoolSize());
        logger.info("活動的執行緒數量:"+executor.getPoolSize());

        //等待所有執行緒完畢
        try {
            n.await();
        } catch (InterruptedException e) {
            logger.error(e);
        }



        //關閉執行緒池
        executor.shutdown();

    }

    class Task implements Runnable {

        private int taskNum;
        CountDownLatch n;



        public Task(int num,CountDownLatch n) {
            this.taskNum = num;
            this.n= n;
        }

        public void run() {
            try{
                //TODO 做你的事情,擦桌子

            }catch(Exception e){
                logger.error(e);
                logger.info("task "+taskNum+"執行失敗");
            }

            //事情幹完了
            n.countDown();
        }
    }
}