10分鐘手擼Java線程池,yyds!!
最近有不少小伙伴私信我說:看了我在【精通高并發系列】文章中寫的深度解析線程池源碼部分的文章,但是還是有些不明白線程池的實現原理。問我能不能手寫一個簡單的線程池,幫助讀者深刻理解線程池的原理。
這不,我熬夜肝了這篇文章。
在【精通高并發系列】的文章中,我們曾經深度解析過線程池的源碼,從源碼層面深度解析了線程池的實現原理。
其實,源碼是原理落地的最直接體現,看懂源碼對于深刻理解原理有著很大的幫助。但是不少小伙伴看源碼時,總覺得源碼太枯燥了,看不懂。
那今天,我們就一起花10分鐘手擼一個極簡版的Java線程池,讓小伙伴們更好的理解線程池的核心原理。
本文的整體結構如下所示。
Java線程池核心原理
看過Java線程池源碼的小伙伴都知道,在Java線程池中最核心的類就是ThreadPoolExecutor,而在ThreadPoolExecutor類中最核心的構造方法就是帶有7個參數的構造方法,如下所示。
- public ThreadPoolExecutor(int corePoolSize,
- int maximumPoolSize,
- long keepAliveTime,
- TimeUnit unit,
- BlockingQueue<Runnable> workQueue,
- ThreadFactory threadFactory,
- RejectedExecutionHandler handler)
各參數的含義如下所示。
- corePoolSize:線程池中的常駐核心線程數。
- maximumPoolSize:線程池能夠容納同時執行的最大線程數,此值大于等于1。
- keepAliveTime:多余的空閑線程存活時間,當空間時間達到keepAliveTime值時,多余的線程會被銷毀直到只剩下corePoolSize個線程為止。
- unit:keepAliveTime的單位。
- workQueue:任務隊列,被提交但尚未被執行的任務。
- threadFactory:表示生成線程池中工作線程的線程工廠,用戶創建新線程,一般用默認即可。
- handler:拒絕策略,表示當線程隊列滿了并且工作線程大于等于線程池的最大顯示數(maxnumPoolSize)時,如何來拒絕請求執行的runnable的策略。
并且Java的線程池是通過 生產者-消費者模式 實現的,線程池的使用方是生產者,而線程池本身就是消費者。
Java線程池的核心工作流程如下圖所示。
手擼Java線程池
我們自己手動實現的線程池要比Java自身的線程池簡單的多,我們去掉了各種復雜的處理方式,只保留了最核心的原理:線程池的使用者向任務隊列中添加任務,而線程池本身從任務隊列中消費任務并執行任務。
只要理解了這個核心原理,接下來的代碼就簡單多了。在實現這個簡單的線程池時,我們可以將整個實現過程進行拆解。拆解后的實現流程為:定義核心字段、創建內部類WorkThread、創建ThreadPool類的構造方法和創建執行任務的方法。
定義核心字段
首先,我們創建一個名稱為ThreadPool的Java類,并在這個類中定義如下核心字段。
- DEFAULT_WORKQUEUE_SIZE:靜態常量,表示默認的阻塞隊列大小。
- workQueue:模擬實際的線程池使用阻塞隊列來實現生產者-消費者模式。
- workThreads:模擬實際的線程池使用List集合保存線程池內部的工作線程。
核心代碼如下所示。
- //默認阻塞隊列大小
- private static final int DEFAULT_WORKQUEUE_SIZE = 5;
- //模擬實際的線程池使用阻塞隊列來實現生產者-消費者模式
- private BlockingQueue<Runnable> workQueue;
- //模擬實際的線程池使用List集合保存線程池內部的工作線程
- private List<WorkThread> workThreads = new ArrayList<WorkThread>();
創建內部類WordThread
在ThreadPool類中創建一個內部類WorkThread,模擬線程池中的工作線程。主要的作用就是消費workQueue中的任務,并執行任務。由于工作線程需要不斷從workQueue中獲取任務,所以,這里使用了while(true)循環不斷嘗試消費隊列中的任務。
核心代碼如下所示。
- //內部類WorkThread,模擬線程池中的工作線程
- //主要的作用就是消費workQueue中的任務,并執行
- //由于工作線程需要不斷從workQueue中獲取任務,使用了while(true)循環不斷嘗試消費隊列中的任務
- class WorkThread extends Thread{
- @Override
- public void run() {
- //不斷循環獲取隊列中的任務
- while (true){
- //當沒有任務時,會阻塞
- try {
- Runnable workTask = workQueue.take();
- workTask.run();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
創建ThreadPool類的構造方法
這里,我們為ThreadPool類創建兩個構造方法,一個構造方法中傳入線程池的容量大小和阻塞隊列,另一個構造方法中只傳入線程池的容量大小。
核心代碼如下所示。
- //在ThreadPool的構造方法中傳入線程池的大小和阻塞隊列
- public ThreadPool(int poolSize, BlockingQueue<Runnable> workQueue){
- this.workQueue = workQueue;
- //創建poolSize個工作線程并將其加入到workThreads集合中
- IntStream.range(0, poolSize).forEach((i) -> {
- WorkThread workThread = new WorkThread();
- workThread.start();
- workThreads.add(workThread);
- });
- }
- //在ThreadPool的構造方法中傳入線程池的大小
- public ThreadPool(int poolSize){
- this(poolSize, new LinkedBlockingQueue<>(DEFAULT_WORKQUEUE_SIZE));
- }
創建執行任務的方法
在ThreadPool類中創建執行任務的方法execute(),execute()方法的實現比較簡單,就是將方法接收到的Runnable任務加入到workQueue隊列中。
核心代碼如下所示。
- //通過線程池執行任務
- public void execute(Runnable task){
- try {
- workQueue.put(task);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
完整源碼
這里,我們給出手動實現的ThreadPool線程池的完整源代碼,如下所示。
- package io.binghe.thread.pool;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.BlockingQueue;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.stream.IntStream;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 自定義線程池
- */
- public class ThreadPool {
- //默認阻塞隊列大小
- private static final int DEFAULT_WORKQUEUE_SIZE = 5;
- //模擬實際的線程池使用阻塞隊列來實現生產者-消費者模式
- private BlockingQueue<Runnable> workQueue;
- //模擬實際的線程池使用List集合保存線程池內部的工作線程
- private List<WorkThread> workThreads = new ArrayList<WorkThread>();
- //在ThreadPool的構造方法中傳入線程池的大小和阻塞隊列
- public ThreadPool(int poolSize, BlockingQueue<Runnable> workQueue){
- this.workQueue = workQueue;
- //創建poolSize個工作線程并將其加入到workThreads集合中
- IntStream.range(0, poolSize).forEach((i) -> {
- WorkThread workThread = new WorkThread();
- workThread.start();
- workThreads.add(workThread);
- });
- }
- //在ThreadPool的構造方法中傳入線程池的大小
- public ThreadPool(int poolSize){
- this(poolSize, new LinkedBlockingQueue<>(DEFAULT_WORKQUEUE_SIZE));
- }
- //通過線程池執行任務
- public void execute(Runnable task){
- try {
- workQueue.put(task);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- //內部類WorkThread,模擬線程池中的工作線程
- //主要的作用就是消費workQueue中的任務,并執行
- //由于工作線程需要不斷從workQueue中獲取任務,使用了while(true)循環不斷嘗試消費隊列中的任務
- class WorkThread extends Thread{
- @Override
- public void run() {
- //不斷循環獲取隊列中的任務
- while (true){
- //當沒有任務時,會阻塞
- try {
- Runnable workTask = workQueue.take();
- workTask.run();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
沒錯,我們僅僅用了幾十行Java代碼就實現了一個極簡版的Java線程池,沒錯,這個極簡版的Java線程池的代碼卻體現了Java線程池的核心原理。
接下來,我們測試下這個極簡版的Java線程池。
編寫測試程序
測試程序也比較簡單,就是通過在main()方法中調用ThreadPool類的構造方法,傳入線程池的大小,創建一個ThreadPool類的實例,然后循環10次調用ThreadPool類的execute()方法,向線程池中提交的任務為:打印當前線程的名稱--->> Hello ThreadPool。
整體測試代碼如下所示。
- package io.binghe.thread.pool.test;
- import io.binghe.thread.pool.ThreadPool;
- import java.util.stream.IntStream;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 測試自定義線程池
- */
- public class ThreadPoolTest {
- public static void main(String[] args){
- ThreadPool threadPool = new ThreadPool(10);
- IntStream.range(0, 10).forEach((i) -> {
- threadPool.execute(() -> {
- System.out.println(Thread.currentThread().getName() + "--->> Hello ThreadPool");
- });
- });
- }
- }
接下來,運行ThreadPoolTest類的main()方法,會輸出如下信息。
- Thread-0--->> Hello ThreadPool
- Thread-9--->> Hello ThreadPool
- Thread-5--->> Hello ThreadPool
- Thread-8--->> Hello ThreadPool
- Thread-4--->> Hello ThreadPool
- Thread-1--->> Hello ThreadPool
- Thread-2--->> Hello ThreadPool
- Thread-5--->> Hello ThreadPool
- Thread-9--->> Hello ThreadPool
- Thread-0--->> Hello ThreadPool
至此,我們自定義的Java線程池就開發完成了。
總結
線程池的核心原理其實并不復雜,只要我們耐心的分析,深入其源碼理解線程池的核心本質,你就會發現線程池的設計原來是如此的優雅。希望通過這個手寫線程池的小例子,能夠讓你更好的理解線程池的核心原理。
本文轉載自微信公眾號「冰河技術」,可以通過以下二維碼關注。轉載本文請聯系冰河技術公眾號。