java實現手寫一個簡單版的線程池
有些人可能對線程池比較陌生,并且更不熟悉線程池的工作原理。所以他們在使用線程的時候,多數情況下都是new Thread來實現多線程。但是,往往良好的多線程設計大多都是使用線程池來實現的。 為什么要使用線程 降低資源的消耗。降低線程創(chuàng)建和銷毀的資源消耗。提高響應速度:線程的創(chuàng)建時間為T1,執(zhí)行時間T2,銷毀時間T3,免去T1和T3的時間提高線程的可管理性
下圖所示為線程池的實現原理:調用方不斷向線程池中提交任務;線程池中有一組線程,不斷地從隊列中取任務,這是一個典型的生產者-消費者模型。

要實現一個線程池,有幾個問題需要考慮:
- 隊列設置多長?如果是無界的,調用方不斷往隊列中方任務,可能導致內存耗盡。如果是有界的,當隊列滿了之后,調用方如何處理?
- 線程池中的線程個數是固定的,還是動態(tài)變化的?
- 每次提交新任務,是放入隊列?還是開新線程
- 當沒有任務的時候,線程是睡眠一小段時間?還是進入阻塞?如果進入阻塞,如何喚醒?
針對問題4,有3種做法:
- 不使用阻塞隊列,只使用一般的線程安全的隊列,也無阻塞/喚醒機制。當隊列為空時,線程池中的線程只能睡眠一會兒,然后醒來去看隊列中有沒有新任務到來,如此不斷輪詢。
- 不使用阻塞隊列,但在隊列外部,線程池內部實現了阻塞/喚醒機制
- 使用阻塞隊列
很顯然,做法3最完善,既避免了線程池內部自己實現阻塞/喚醒機制的麻煩,也避免了做法1的睡眠/輪詢帶來的資源消耗和延遲。
現在來帶大家手寫一個簡單的線程池,讓大家更加理解線程池的工作原理
實戰(zhàn):手寫簡易線程池
根據上圖可以知道,實現線程池需要一個阻塞隊列+存放線程的容器
/**
* Five在努力
* 自定義線程池
*/
public class ThreadPool {
/** 默認線程池中的線程的數量 */
private static final int WORK_NUM = 5;
/** 默認處理任務的數量 */
private static final int TASK_NUM = 100;
/** 存放任務 */
private final BlockingQueue<Runnable> taskQueue;
private final Set<WorkThread> workThreads;//保存線程的集合
private int workNumber;//線程數量
private int taskNumber;//任務數量
public ThreadPool(){
this(WORK_NUM , TASK_NUM);
}
public ThreadPool(int workNumber , int taskNumber) {
if (taskNumber<=0){
taskNumber = TASK_NUM;
}
if (workNumber<=0){
workNumber = WORK_NUM;
}
this.taskQueue = new ArrayBlockingQueue<Runnable>(taskNumber);
this.workNumber = workNumber;
this.taskNumber = taskNumber;
workThreads = new HashSet<>();
//工作線程準備好了
//啟動一定數量的線程數,從隊列中獲取任務處理
for (int i=0;i<workNumber;i++) {
WorkThread workThread = new WorkThread("thead_"+i);
workThread.start();
workThreads.add(workThread);
}
}
/**
* 線程池執(zhí)行任務的方法,其實就是往BlockingQueue中添加元素
* @param task
*/
public void execute(Runnable task) {
try {
taskQueue.put(task);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 銷毀線程池
*/
public void destroy(){
System.out.println("ready close pool...");
for (WorkThread workThread : workThreads) {
workThread.stopWorker();
workThread = null;//help gc
}
workThreads.clear();
}
/** 內部類,工作線程的實現 */
private class WorkThread extends Thread{
public WorkThread(String name){
super();
setName(name);
}
@Override
public void run() {
while (!interrupted()) {
try {
Runnable runnable = taskQueue.take();//獲取任務
if (runnable !=null) {
System.out.println(getName()+" ready execute:"+runnable.toString());
runnable.run();//執(zhí)行任務
}
runnable = null;//help gc
} catch (Exception e) {
interrupt();
e.printStackTrace();
}
}
}
public void stopWorker(){
interrupt();
}
}
}
上面代碼定義了默認的線程數量和默認處理任務數量,同時用戶也可以自定義線程數量和處理任務數量。用BlockingQueue阻塞隊列來存放任務。用set來存放工作線程,set的好處就不用多說了。懂的都懂
構造方法中new對象的時候,循環(huán)啟動線程,并把線程放入set中。WorkThread實現Thread,run方法實現也很簡單,因為有一個stop方法,所以這里需要while判斷,之后從taskQueue隊列中,獲取任務。如何獲取不到就阻塞,獲取到的話runnable.run();就執(zhí)行任務,之后把任務變成null
銷毀線程只需要遍歷set,把每個線程停止,并且變?yōu)閚ull就行了
執(zhí)行線程任務execute,只需要從往阻塞隊列中添加任務就行了
測試一下:
public class TestMySelfThreadPool {
private static final int TASK_NUM = 50;//任務的個數
public static void main(String[] args) {
ThreadPool myPool = new ThreadPool(3,50);
for (int i=0;i<TASK_NUM;i++) {
myPool.execute(new MyTask("task_"+i));
}
}
static class MyTask implements Runnable{
private String name;
public MyTask(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("task :"+name+" end...");
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "name = "+name;
}
}
}

結果ok。沒什么問題
到此這篇關于java實現手寫一個簡單版的線程池的文章就介紹到這了,更多相關java 手寫線程池內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決JSONObject.toJSONString()輸出null的問題
這篇文章主要介紹了解決JSONObject.toJSONString()輸出null的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
java基礎(System.err和System.out)詳解
下面小編就為大家?guī)硪黄猨ava基礎(System.err和System.out)詳解。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06

