手寫一個簡單的線程池

手寫一個簡單的線程池

項目倉庫:https://gitee.com/bossDuy/hand-tearing-thread-pool
基于一個b站up的課程:https://www.bilibili.com/video/BV1cJf2YXEw3/?spm_id_from=333.788.videopod.sections&vd_source=4cda4baec795c32b16ddd661bb9ce865

理解線程池的原理

線程池就是為了減少頻繁的創建和銷毀線程帶來的性能損耗,工作原理:

在這里插入圖片描述

簡單的說:線程池就是有一個存放線程的集合和一個存放任務的阻塞隊列。當提交一個任務的時候,判斷核心線程是否滿了,沒滿就會創建一個核心線程加入線程池并且執行任務,核心線程是不會被銷毀的即使沒有任務執行;滿了就會放入任務隊列等待;如果隊列滿了的話就會創建非核心線程進行執行任務,這些非核心線程在不執行任務的時候就會等一段時間銷毀(配置的過期時間),如果創建的線程達到了最大線程數,那么就會執行拒絕策略。

可以簡要整理如下:

提交任務 -> 核心任務是否已滿為滿,創建核心線程并執行任務已滿,則加入任務隊列隊列未滿 -> 等待執行隊列已滿 -> 創建非核心線程達到線程最大數量 -> 拒絕策略未達到最大數量 -> 執行任務

自己實現簡單的線程池

第一步:實現了一個線程復用的線程池

package com.yb0os1;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class MyThreadPool {//1、線程什么時候創建?/**核心線程中我們要保證線程是可以復用的,那么就不可以直接new Thread(task).start(); 這樣執行完task線程就會被銷毀了我們將接收到的任務對象放到隊列中,然后線程從隊列中取出任務,通過任務的run方法進行調用,這樣就是在該線程上調用任務,并且調用完后不會銷毀線程*///2、我們一開始使用 while (true) if(!tasks.isEmpty()) Runnable task = tasks.remove(0);/**這樣如果任務隊列一直為空就會一循環,消耗cpu資源。此時就是阻塞隊列出現了,當為空阻塞等待 非空執行*/BlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<>(1024);Thread thread = new Thread(()->{while (true){if(!taskQueue.isEmpty()){try {Runnable task = taskQueue.take();task.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}}},"唯一線程");{thread.start();//啟動線程}public void execute(Runnable task){taskQueue.offer(task);//向隊列添加元素 盡量是否offer 滿則返回false  add滿則排除異常}
}
package com.yb0os1;public class Main {public static void main(String[] args) {MyThreadPool myThreadPool = new MyThreadPool();for (int i = 0; i < 5; i++) {myThreadPool.execute(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {//InterruptedException這個是線程中斷異常,// 這個異常一般都是線程在等待或者阻塞中被中斷了就會拋出的,// sleep wait等等都是有,除了LockSupport.park 這個會記錄中斷位 不會拋出這個異常e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"執行完畢");});}System.out.println("主線程沒有被阻塞");}
}

測試結果:

在這里插入圖片描述

第二步:實現多個線程復用的線程池

package com.yb0os1;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;public class MyThreadPool {//任務隊列private final BlockingQueue<Runnable> taskQueue;//核心線程的數量private final int corePoolSize;//最大線程的數量private final int maxPoolSize;private final int keepAliveTime;private final TimeUnit unit;public MyThreadPool(int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> taskQueue) {this.corePoolSize = corePoolSize;this.maxPoolSize = maxPoolSize;this.keepAliveTime = keepAliveTime;this.unit = unit;this.taskQueue = taskQueue;}//核心線程List<Thread> coreList = new ArrayList<>();//非核心線程List<Thread> supportList = new ArrayList<>();//添加元素和判斷長度不是原子的,所以存在線程安全問題 可以加鎖 CAS等解決public void execute(Runnable command) {//目前線程列表中線程數量小于核心線程的數量,則創建線程if (coreList.size() < corePoolSize) {Thread thread = new CoreThread();coreList.add(thread);thread.start();
//            return;}//成功添加到阻塞隊列if (taskQueue.offer(command)) {return;}//任務隊列也滿了 需要創建非核心線程//核心線程滿 任務隊列滿 但是非核心線程沒有滿才可以添加if (coreList.size() + supportList.size() < maxPoolSize) {Thread thread = new SupportThread();supportList.add(thread);thread.start();return;}//我們創建完線程之后 并沒有處理剛才的command 不能確定是否隊列真的滿了if (!taskQueue.offer(command)) {//真的滿了 拋出異常throw new RuntimeException("線程池已滿");}}class CoreThread extends Thread {@Overridepublic void run() {while (true) {try {Runnable task = taskQueue.take();task.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}class SupportThread extends Thread {@Overridepublic void run() {while (true) {try {Runnable command  = taskQueue.poll(keepAliveTime, unit);//等待一秒沒有獲取就會返回nullif (command  == null) {//線程結束break;}command.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName()+"非核心線程結束");supportList.remove(Thread.currentThread());System.out.println("當前非核心線程數量為:" + supportList.size());}}
}
package com.yb0os1;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args) {MyThreadPool myThreadPool = new MyThreadPool(2,4,1, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));for (int i = 0; i < 4; i++) {myThreadPool.execute(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {//InterruptedException這個是線程中斷異常,// 這個異常一般都是線程在等待或者阻塞中被中斷了就會拋出的,// sleep wait等等都是有,除了LockSupport.park 這個會記錄中斷位 不會拋出這個異常e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"執行完畢");});}System.out.println("主線程沒有被阻塞");}
}

存在問題,任務沒有被正確的執行:

在這里插入圖片描述

b站評論區指出的:if (blockingQueue.offer(command)) { return; } 這里如果任務成功放入隊列,方法就直接 return 了。 但在 創建 SupportThread 的邏輯中,沒有保證這個任務會被執行,因為 offer() 失敗后你才創建新線程。 但 command 并沒有交給這個新線程,而是再次嘗試 offer(),如果失敗就直接走拒絕策略了。 這樣的話,可能 SupportThread 已經啟動,但任務卻沒被執行。

理解:如果隊列滿了,我們創建非核心線程,但是并沒有將這任務直接交給我們創建的新線程,而是再次嘗試加入隊列中,這就導致了一個不確定的狀態:

  1. 如果此時隊列還是滿的(offer 返回 false),就會直接拋出異常,任務未被執行
  2. 如果隊列此時恰好有空間(可能因為其他線程剛剛完成了任務,從而騰出了隊列空間),那么任務會被放入隊列,后續由某個線程(可能是核心線程,也可能是其他非核心線程)從隊列中取出并執行。但新創建的非核心線程可能并沒有真正處理這個任務。

解決方案:如果隊列滿了,我們要創建非核心線程并且由這個線程執行任務

也可以說 讓線程執行當前 command 之后,再從 queue 中拿任務

第三步:修復bug 設計拒絕策略

package com.yb0os1;import com.yb0os1.reject.DiscardRejectHandle;
import com.yb0os1.reject.ThrowRejectHandle;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args) {MyThreadPool myThreadPool = new MyThreadPool(2,4,1, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),new DiscardRejectHandle());for (int i = 0; i < 8; i++) {int finalI = i;myThreadPool.execute(()->{try {Thread.sleep(100);} catch (InterruptedException e) {//InterruptedException這個是線程中斷異常,// 這個異常一般都是線程在等待或者阻塞中被中斷了就會拋出的,// sleep wait等等都是有,除了LockSupport.park 這個會記錄中斷位 不會拋出這個異常e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"執行完畢---"+ finalI);});}System.out.println("主線程沒有被阻塞");}
}
package com.yb0os1;import com.yb0os1.reject.RejectHandle;import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;public class MyThreadPool {public BlockingQueue<Runnable> getTaskQueue() {return taskQueue;}//任務隊列private final BlockingQueue<Runnable> taskQueue;//核心線程的數量private final int corePoolSize;//最大線程的數量private final int maxPoolSize;private final int keepAliveTime;private final TimeUnit unit;//拒絕策略private final RejectHandle rejectHandle;public MyThreadPool(int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> taskQueue,  RejectHandle rejectHandle) {this.corePoolSize = corePoolSize;this.maxPoolSize = maxPoolSize;this.keepAliveTime = keepAliveTime;this.unit = unit;this.taskQueue = taskQueue;this.rejectHandle = rejectHandle;}//核心線程List<Thread> coreList = new ArrayList<>();//非核心線程List<Thread> supportList = new ArrayList<>();//添加元素和判斷長度不是原子的,所以存在線程安全問題 可以加鎖 CAS等解決public void execute(Runnable command) {//目前線程列表中線程數量小于核心線程的數量,則創建線程if (coreList.size() < corePoolSize) {Thread thread = new CoreThread(command);coreList.add(thread);thread.start();return;}//成功添加到阻塞隊列if (taskQueue.offer(command)) {return;}//任務隊列也滿了 需要創建非核心線程//核心線程滿 任務隊列滿 但是非核心線程沒有滿才可以添加if (coreList.size() + supportList.size() < maxPoolSize) {Thread thread = new SupportThread(command);supportList.add(thread);thread.start();return;}//我們創建完線程之后 并沒有處理剛才的command 不能確定是否隊列真的滿了if (!taskQueue.offer(command)) {//真的滿 使用拒絕策略rejectHandle.reject(command,this);}}//優先處理傳過來的 然后再去阻塞隊列中獲取class CoreThread extends Thread {private final Runnable command;CoreThread(Runnable command) {this.command = command;}@Overridepublic void run() {command.run();while (true) {try {Runnable task = taskQueue.take();System.out.println("核心線程"+Thread.currentThread().getName()+"正在執行任務");task.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}class SupportThread extends Thread {private final Runnable command;SupportThread(Runnable command) {this.command = command;}@Overridepublic void run() {command.run();while (true) {try {Runnable command  = taskQueue.poll(keepAliveTime, unit);//等待一秒沒有獲取就會返回nullif (command  == null) {//線程結束break;}command.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName()+"非核心線程結束");supportList.remove(Thread.currentThread());
//            System.out.println("當前非核心線程數量為:" + supportList.size());}}
}
package com.yb0os1.reject;import com.yb0os1.MyThreadPool;public interface RejectHandle {void reject(Runnable command, MyThreadPool myThreadPool);
}
package com.yb0os1.reject;import com.yb0os1.MyThreadPool;public class DiscardRejectHandle implements RejectHandle{@Overridepublic void reject(Runnable command, MyThreadPool myThreadPool) {myThreadPool.getTaskQueue().poll();System.out.println("任務被丟棄");}
}
package com.yb0os1.reject;import com.yb0os1.MyThreadPool;public class ThrowRejectHandle implements RejectHandle{@Overridepublic void reject(Runnable command, MyThreadPool myThreadPool) {throw new RuntimeException("線程池已滿");}
}

思考

在這里插入圖片描述

1.你能給線程池增加一個shutdown功能嗎

答:關閉線程池分兩種情況, 一個是清空任務隊列、線程全部完成任務后關閉; 二是等線程完成后直接關,不管隊列中的任務。

2、怎么理解拒絕策略

答:首先它是一個策略模式,在線程池的代碼中,當任務隊列滿時就會觸發該接口的方法,所以我們只要實現這個接口方法,再把實現類傳入線程池即可,并且方法里還可以拿到被拒絕的任務、線程池對象來實現自己的拒絕邏輯。

3、ThreadFactory參數

答:這個參數是線程池用來創建核心、輔助線程的方法,我們可以自定義線程名稱等參數。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/82041.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82041.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82041.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

手機打電話時由對方DTMF響應切換多級IVR語音菜單(完結)

手機打電話時由對方DTMF響應切換多級IVR語音菜單&#xff08;完結&#xff09; --本地AI電話機器人 上一篇&#xff1a;手機打電話時由對方DTMF響應切換多級IVR語音菜單&#xff08;話術腳本與實戰&#xff09; 下一篇&#xff1a;編寫中 一、前言 經過前面幾個篇章的詳細闡…

Android.mk解析

一、變量說明: 1.LOCAL_PATH:= $(call my-dir) 此行代碼在Android.mk的開頭,用于給出當前文件的路徑 LOCAL_PATH 用于在開發樹中查找源文件 宏函數’my-dir’, 由編譯系統提供,用于返回當前路徑(即包含Android.mk file文件的目錄) 2.LOCAL_PACKAGE_NAME := SecSettings …

ip地址改了網絡還能用嗎?ip地址改了有什么后果

當用戶發現自己的網絡出現異常時&#xff0c;常常會疑惑&#xff1a;如果IP地址被更改&#xff0c;網絡是否還能正常使用&#xff1f;要解答這個問題&#xff0c;需要從IP地址的作用、修改方式以及網絡配置等多個角度來分析。 一、IP地址的作用 IP地址是設備在網絡中的唯一標識…

Python-Django系列—日志

Python 程序員通常會在其代碼中使用 print() 作為一種快速和方便的調試工具。使用日志框架只比這多花一點點工夫&#xff0c;但更加優雅和靈活。除了用于調試之外&#xff0c;日志還可以為您提供有關應用程序狀態和健康狀況的更多信息&#xff0c;而且這些信息結構更清晰。 一…

ArcGIS Pro對圖斑進行等比例、等面積、等寬度的分割

ArcGIS全系列實戰視頻教程——9個單一課程組合系列直播回放_arcgis視頻教程我要自學網-CSDN博客 4大遙感軟件&#xff01;遙感影像解譯&#xff01;ArcGISENVIErdaseCognition_遙感解譯軟件-CSDN博客 今天介紹一下ArcGIS Pro對圖斑進行等比例、等面積、等寬度的分割&#xff0…

”故茗”茶文化網站

摘 要 計算機網絡發展到現在已經好幾十年了&#xff0c;在理論上面已經有了很豐富的基礎&#xff0c;并且在現實生活中也到處都在使用&#xff0c;可以說&#xff0c;經過幾十年的發展&#xff0c;互聯網技術已經把地域信息的隔閡給消除了&#xff0c;讓整個世界都可以即時通話…

【和春筍一起學C++】(十五)字符串作為函數參數

1. char指針作為函數參數 在C語言中&#xff0c;表示字符串的方式有3種&#xff1a; char數組用引號括起的字符串常量char指針 這3種形式都可以將其作為實參傳遞給函數中的參數&#xff08;char*&#xff09;&#xff0c;因此函數的形參需要使用char*類型。將字符串作為參數…

VueRouter路由組件的用法介紹

1.1、<router-link>標簽 <router-link>標簽的作用是實現路由之間的跳轉功能&#xff0c;默認情況下&#xff0c;<router-link>標簽是采用超鏈接<a>標簽顯示的&#xff0c;通過to屬性指定需要跳轉的路由地址。當然&#xff0c;如果你不想使用默認的<…

【C/C++】勝者樹與敗者樹:多路歸并排序的利器

文章目錄 勝者樹與敗者樹&#xff1a;多路歸并排序的利器1 勝者樹簡介1.1 定義1.2 勝者樹結構與原理1.2.1 構造流程1.2.2 歸并過程 2 敗者樹簡介2.1 背景場景2.2 基本定義2.3 敗者樹結構和原理2.3.1 樹的構造&#xff08;初始建樹&#xff09;2.3.2 查詢和更新 3 勝者樹 vs 敗者…

零基礎設計模式——第二部分:創建型模式 - 原型模式

第二部分&#xff1a;創建型模式 - 5. 原型模式 (Prototype Pattern) 我們已經探討了單例、工廠方法、抽象工廠和生成器模式。現在&#xff0c;我們來看創建型模式的最后一個主要成員——原型模式。這種模式關注的是通過復制現有對象來創建新對象&#xff0c;而不是通過傳統的…

C++(初階)(十九)——紅黑樹

紅黑樹 紅黑樹概念規則實現結點插入變色變色參考代碼&#xff1a; 查找查找參考代碼 遍歷 紅黑樹檢查完整代碼 概念 紅?樹是?棵?叉搜索樹。它的每個結點增加?個存儲位來表示結點的顏?&#xff0c;可以是紅色或者黑色&#xff08;并不會出現第三種顏色&#xff09;。 通過…

Mistral AI 開源最新 Small 模型——Devstral-Small-2505

Devstral 是一款專為軟件工程任務設計的代理型大語言模型&#xff08;LLM&#xff09;&#xff0c;由 Mistral AI 和 All Hands AI 合作開發 &#x1f64c;。Devstral 擅長使用工具探索代碼庫、編輯多個文件以及驅動軟件工程代理。該模型在 SWE-bench 上表現出色&#xff0c;使…

CDGA|一線二線企業數據治理項目目前發展狀況

一線城市與二線城市企業在數據治理項目的發展狀況上存在一定差異&#xff0c;主要體現在目標、資源投入、策略實施以及文化培育等方面。 一線城市企業數據治理項目發展狀況 ?數據治理目標全面系統?&#xff1a; ?數據質量與安全?&#xff1a;一線城市的大型企業通常擁有海量…

Lyra學習筆記1地圖角色加載流程

目錄 1 地圖加載流程1.1 默認Experience的加載1.2 加載角色1.3 加載場景中的幾個傳送點 2 幾個內建類的筆記2.1 UDataAsset2.2 UAssetManager 純個人筆記&#xff0c;有錯誤歡迎指正&#xff0c;學習階段基本看到不會的就寫一寫&#xff0c;最后有時間會梳理整體結構 先看完了官…

SurfaceFlinger及Android應用RenderThread角度觀察Jank丟幀卡頓

SurfaceFlinger及Android應用RenderThread角度觀察Jank丟幀卡頓 CPU、GPU、Display 三個部分&#xff1a;CPU 負責計算幀數據&#xff0c;把計算好的數據交給 GPU&#xff0c;GPU 會對圖形數據進行渲染&#xff0c;渲染好后放到 buffer &#xff08;圖像緩沖區&#xff09;存起…

《牛客》數組中出現次數超過一半的數字

牛客的刷題之路不停歇 ??? 不積跬步無以至千里&#xff0c;不積小流無以成江海 The harder you work,the luckier you will be 題目及示例 題目鏈接 描述 給一個長度為 n 的數組&#xff0c;數組中有一個數字出現的次數超過數組長度的一半&#xff0c;請找出這個數字。 例…

七彩喜康養護理——科技賦能下的全周期健康守護

在當今社會&#xff0c;隨著人們健康意識的不斷提高&#xff0c;護理行業逐漸走向專業化、精細化&#xff0c;而七彩喜智養護理作為一種新興的護理方式&#xff0c;逐漸受到了廣泛的關注和應用。 它不僅僅是針對單一病癥的治療護理&#xff0c;而是一種全面的、全方位的健康管…

【爬蟲】12306自動化購票

上文&#xff1a; 【爬蟲】12306查票-CSDN博客 下面是簡單的自動化進行搶票&#xff0c;只寫到預定票&#xff0c;沒有寫完登陸&#xff0c; 跳出登陸后與上述代碼同理修改即可。 感覺xpath最簡單&#xff0c;復制粘貼&#xff1a; 還有很多寫法&#xff1a; 官網地址&#…

Java設計模式之組合模式:從入門到精通(保姆級教程)

文章目錄 1. 組合模式概述1.1 專業定義1.2 通俗解釋1.3 模式結構2. 組合模式詳細解析2.1 模式優缺點2.2 適用場景3. 組合模式實現詳解3.1 基礎實現3.2 代碼解析4. 組合模式進階應用4.1 透明式 vs 安全式組合模式4.2 組合模式與遞歸4.3 組合模式與迭代器5. 組合模式在實際開發中…

游戲如何應對反編譯工具dnspy

Unity Mono 是 Unity 引擎默認的腳本運行時環境&#xff0c;由跨平臺的開源 .NET 框架實現&#xff0c;它允許開發者使用 C# 等編程語言編寫游戲邏輯&#xff0c;憑借簡單易用的開發環境和高效的腳本編譯速度&#xff0c;得到了眾多游戲的青睞。 在 Mono 模式下&#xff0c;游…