Java併發篇—線程池入門掃盲指南

Java線程池入門掃盲

線程池的實現原理

線程池執行任務的主要流程

線程池的主要處理流程

主要處理流程:java

(1)判斷核心線程池是否已滿,若是還沒滿則建立線程執行任務,不然進入下一步數據庫

(2)判斷工做隊列是否已滿,若是沒滿則將該任務放入工做隊列等待線程來執行,不然進入下一步編程

(3)判斷線程池中的線程是否都處於工做狀態,若是不是則新建立一個線程來執行任務,沒有處於工做狀態的線程被淘汰,不然按照飽和策略處理該任務。併發

ThreadPoolExecutor實現原理

執行流程

1:當一個任務提交時,若是CorePool中的核心線程少於CorePoolSize,則建立一個新線程執行任務(須要全局鎖源碼分析

2:若是CorePool中沒有空閒的線程,那麼加入BlockingQueue等待覈心線程拉取任務執行性能

3:若是BlockingQueue已滿,建立新線程後若是大於maximumPoolSize就跳轉到4拒絕執行任務,若是小於就建立新線程執行任務(須要全局鎖ui

4:四種不一樣的拒絕策略,經過rejectedExecution()方法執行。spa

須要獲取全局鎖致使線程池的性能大大降低,應該儘可能避免產生步驟1和步驟3線程

execute() 源碼分析

public void execute(Runnable command) {
    // 若是任務爲空則拋出異常
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 1.工做線程數小於核心線程池大小則添加工做線程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        // 更新線程池工做狀態
        c = ctl.get();
    }
    // 2.判斷線程池是否處於工做狀態,若是是則嘗試把任務放入阻塞隊列中
    if (isRunning(c) && workQueue.offer(command)) {
        // 3.再次檢查是否應該回滾任務添加線程,防止任務放入阻塞隊列後線程池down或者工做線程死亡
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            //double check時線程池down了,該任務沒法執行
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 4.沒法加入阻塞隊列或者線程池已down,拒絕任務執行
    else if (!addWorker(command, false))
        reject(command);
}
複製代碼

工做線程Worker執行流程

工做者線程執行流程解析

1: execute()方法建立一個Worker線程執行當前任務rest

2:若是Worker線程數目等於CorePoolSize,則加入到阻塞隊列中,等待Worker取出來執行任務

3:若是BlockingQueue已滿,若是Worker線程數小於maximumPoolSize則建立新線程執行任務

4:反覆執行(1)(2)(3)

線程池的使用

重點是設置參數,參數設得好,線上沒煩惱

圍繞下面幾個問題思考如何設置線程池

線程池的大小?例如在2G內存裏配置一個線程池,如何設置線程池大小

執行的任務屬於哪種類型?例如IO密集、CPU密集······

任務是否具備優先級?例如任務的緊急程度、執行時間長短

任務是否具備依賴性?例如依賴數據庫鏈接

建立線程池

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);
複製代碼

1. corePoolSize(線程池基本大小)

每次提交任務時,若是當前線程數小於corePoolSize就會建立一個新線程,即便線程池中有空閒線程,直到線程數等於corePoolSize

能夠提早建立好全部線程並啓動,調用prestartAllCoreThreads()

2. runnableTaskQueue(任務阻塞隊列)

保存等待執行的任務隊列,可使用任意阻塞隊列,例如ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue

3. maximumPoolSize(線程池最大數量)

線程池容許的最大線程數,阻塞隊列中的任務已滿(隊列必須有界)時,下一個任務沒法進入阻塞隊列,若是線程池中的線程數小於最大線程數,則嘗試在線程池中繼續建立線程執行任務

4. ThreadFactory(建立線程的工廠)

給每一個線程設置更有意義的名字的一個工廠,例如ThreadFactoryBuilder工廠

5. RejectedExecutionHandler(飽和策略)

任務拒絕執行時採用的拒絕策略,有四種:

  1. AbortPolicy:拋出異常
  2. CallerRunsPolicy:使用調用者線程執行任務
  3. DiscardOldestPolicy:丟棄隊列中最近的一個任務,並執行當前任務
  4. DiscardPolicy:直接丟棄

6. keepAliveTime(線程活動生命時長)

線程池的工做線程空閒後能夠保持存活的時長。若是任務不少且每一個任務的執行時間很短,能夠調大存活時長提升線程利用率。

7. TimeUnit(生命時長的計算單位)

DAYS/HOURS/MINUTES/MILLISECONDS(毫秒)/MICROSECONDS(微妙)/NANOSECONDS(納秒)

提交任務

兩個核心方法:有返回值的submit()和無返回的execute()

submit()方法

線程池會返回一個Future對象,對象中存儲着任務是否執行成功、返回結果等信息,調用get()方法能夠獲取返回值,在任務未完成前會一直阻塞,能夠設置超時時長get(long, TimeUnit)防止一直阻塞。

Future<Object> future = executor.submit(hasReturnValueTask);
try {
	future.get();    
} catch(Exception e) {
    
} finally {
    // 關閉線程池
    executor.shutdown();
}

複製代碼

關閉線程池

兩個核心方法:shutdown()shutdownNow()

區別:

shutdownNow()會將線程池狀態設置爲STOP,而後嘗試終止全部正在執行任務的線程,並返回等待任務的任務列表

shutdown()會將線程池狀態設置爲SHUTDOWN,而後中斷全部沒有正在執行任務的線程。

如何選擇:

若是任務不必定執行完,可使用shutdownNow(),不然使用shutdown()

調用關閉方法後,調用isShutdown()方法就會返回True,可是確認線程池的關閉還須要調用isTerminated方法,這個方法表示全部的任務都已經關閉了。

if(isShutdown() && isTerminated()) {
	System.out.print("線程池已關閉!");
}
複製代碼

合理地配置線程池

分析任務特性:

任務的性質:CPU密集、IO密集、混合型(配置線程數)

任務的優先級:高、中和低(配置阻塞隊列)

任務的執行時間:長、中和短(配置線程空閒存活時長)

任務的依賴性:是否依賴其餘系統資源,如數據庫鏈接(配置線程數目)

重點說明任務的依賴性如何肯定線程數目,若是線程須要依賴數據庫鏈接,提交SQL後須要等待數據庫返回結果,這個過程CPU是空閒的,CPU空閒時間越長,那麼線程數應該設置得越大,能夠更好地利用CPU。

最好配置阻塞隊列會有界阻塞隊列,能夠監控線程池的工做狀態,若是大量的任務放入阻塞隊列則會不斷報出任務拒絕信息。

線程池的監控

takeCount:線程池須要執行的任務數量

completedTaskCount:線程池在運行過程當中已完成的任務數量

largestPoolSize:線程池曾經建立過的最大線程數量

getPoolSize:線程池的線程數量

getActiveCount:獲取活動的線程數目

能夠經過擴展線程池進行監控,重寫beforExecute()afterExecute()terminated()方法。

總結

到這裏爲止,應該要掌握下面的知識,若是你能很流暢地回答,那麼恭喜你,線程池的基本原理算是過關了

  1. 談談線程池的核心原理?
  2. 談談工做線程的執行流程?
  3. 建立線程池有哪些參數要設置,如何設置參數,考慮因素有哪些?
  4. 談談提交併執行任務的兩個方法及它們的區別?
  5. 談談關閉線程池的兩個方法及它們的區別?

總結不易,若是閱讀完這篇文章對你有小小的收穫,你的一個小小的點贊能讓我高興一成天!

巨人的肩膀:

《Java併發編程的藝術》

相關文章
相關標籤/搜索