Java實現(xiàn)手寫乞丐版線程池的示例代碼
前言
在上篇文章線程池的前世今生當中我們介紹了實現(xiàn)線程池的原理,在這篇文章當中我們主要介紹實現(xiàn)一個非常簡易版的線程池,深入的去理解其中的原理,麻雀雖小,五臟俱全。
線程池的具體實現(xiàn)
線程池實現(xiàn)思路
任務保存到哪里?
在上篇文章線程池的前世今生當中我們具體去介紹了線程池當中的原理。在線程池當中我們有很多個線程不斷的從任務池(用戶在使用線程池的時候不斷的使用execute方法將任務添加到線程池當中)里面去拿任務然后執(zhí)行,現(xiàn)在需要思考我們應該用什么去實現(xiàn)任務池呢?
答案是阻塞隊列,因為我們需要保證在多個線程往任務池里面加入任務的時候并發(fā)安全,JDK已經(jīng)給我們提供了這樣的數(shù)據(jù)結構——BlockingQueue,這個是一個并發(fā)安全的阻塞隊列,他之所以叫做阻塞隊列,是因為我們可以設置隊列當中可以容納數(shù)據(jù)的個數(shù),當加入到隊列當中的數(shù)據(jù)超過這個值的時候,試圖將數(shù)據(jù)加入到阻塞隊列當中的線程就會被掛起。當隊列當中為空的時候,試圖從隊列當中取出數(shù)據(jù)的線程也會被掛起。
線程的設計
在我們自己實現(xiàn)的線程池當中我們定一個Worker類去不斷的從任務池當中取出任務,然后進行執(zhí)行。在我們自己定義的worker當中還需要有一個變量isStopped表示線程是否停止工作。同時在worker當中還需要保存當前是哪個線程在執(zhí)行任務,因此在我們自己設計的woker類當中還需要有一個thisThread變量,保存正在執(zhí)行任務的線程,因此worker的整體設計如下:
package cscore.concurrent.java.threadpool;
import java.util.concurrent.BlockingQueue;
public class Worker implements Runnable {
private Thread thisThread; // 表示正在執(zhí)行任務的線程
private BlockingQueue<Runnable> taskQueue; // 由線程池傳遞過來的任務隊列
private volatile boolean isStopped; // 表示 worker 是否停止工作 需要使用 volatile 保證線程之間的可見性
public Worker(BlockingQueue taskQueue) { // 這個構造方法是在線程池的實現(xiàn)當中會被調用
this.taskQueue = taskQueue;
}
// 線程執(zhí)行的函數(shù)
@Override
public void run() {
thisThread = Thread.currentThread(); // 獲取執(zhí)行任務的線程
while (!isStopped) { // 當線程沒有停止的時候就不斷的去任務池當中取出任務
try {
Runnable task = taskQueue.take(); // 從任務池當中取出任務 當沒有任務的時候線程會被這個方法阻塞
task.run(); // 執(zhí)行任務 任務就是一個 Runnable 對象
} catch (InterruptedException e) {
// do nothing
// 這個地方很重要 你有沒有思考過一個問題當任務池當中沒有任務的時候 線程會被阻塞在 take 方法上
// 如果我們后面沒有任務提交拿他就會一直阻塞 那么我們該如何喚醒他呢
// 答案就在下面的函數(shù)當中 調用線程的 interruput 方法 那么take方法就會產(chǎn)生一個異常 然后我們
// 捕獲到一異常 然后線程退出
}
}
}
public synchronized void stopWorker() {
if (isStopped) {
throw new RuntimeException("thread has been interrupted");
}
isStopped = true;
thisThread.interrupt(); // 中斷線程產(chǎn)生異常
}
public synchronized boolean isStopped() {
return isStopped;
}
}
線程池的參數(shù)
在我們自己實現(xiàn)的線程池當中,我們只需要定義兩個參數(shù)一個是線程的個數(shù),另外一個是阻塞隊列(任務池)當中最大的任務個數(shù)。在我們自己實現(xiàn)的線程池當中還需要有一個變量isStopped表示線程池是否停止工作了,因此線程池的初步設計大致如下:
private BlockingQueue taskQueue; // 任務池
private volatile boolean isStopped; //
private final List<Worker> workers = new ArrayList<>();// 保存所所有的執(zhí)行任務的線程
public ThreadPool(int numThreads, int maxTasks) {
this.taskQueue = new ArrayBlockingQueue(maxTasks);
for (int i = 0; i < numThreads; i++) {
workers.add(new Worker(this.taskQueue));
}
int i = 1;
// 這里產(chǎn)生線程 然后啟動線程
for (Worker worker : workers) {
new Thread(worker, "ThreadPool-" + i + "-thread").start();
i++;
}
}
線程池實現(xiàn)代碼
在上文當中我們大致設計的線程池的初步結構,從上面的結果可以看出當我們造一個ThreadPool對象的時候會產(chǎn)生指定線程的數(shù)目線程并且啟動他們?nèi)?zhí)行任務,現(xiàn)在我們還需要設計的就是如果關閉線程!我們在關閉線程的時候還需要保證所有的任務都被執(zhí)行完成然后才關閉所有的線程,再退出,我們設計這個方法為shutDown。除此之外我們還設計一個函數(shù)可以強制退出,不用執(zhí)行所有的任務了,就直接退出,這個方法為stop。整個線程池實現(xiàn)的代碼如下:
package cscore.concurrent.java.threadpool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ThreadPool {
private BlockingQueue taskQueue;
private volatile boolean isStopped;
private final List<Worker> workers = new ArrayList<>();
public ThreadPool(int numThreads, int maxTasks) {
this.taskQueue = new ArrayBlockingQueue(maxTasks);
for (int i = 0; i < numThreads; i++) {
workers.add(new Worker(this.taskQueue));
}
int i = 1;
for (Worker worker : workers) {
new Thread(worker, "ThreadPool-" + i + "-thread").start();
i++;
}
}
// 下面這個方法是向線程池提交任務
public void execute(Runnable runnable) throws InterruptedException {
if (isStopped) {
// 如果線程池已經(jīng)停下來了,就不在向任務隊列當中提交任務了
System.err.println("thread pool has been stopped, so quit submitting task");
return;
}
taskQueue.put(runnable);
}
// 強制關閉線程池
public synchronized void stop() {
isStopped = true;
for (Worker worker : workers) {
worker.stopWorker();
}
}
public synchronized void shutDown() {
// 先表示關閉線程池 線程就不能再向線程池提交任務
isStopped = true;
// 先等待所有的任務執(zhí)行完成再關閉線程池
waitForAllTasks();
stop();
}
private void waitForAllTasks() {
// 當線程池當中還有任務的時候 就不退出循環(huán)
while (taskQueue.size() > 0)
Thread.yield();
}
}
線程池測試代碼
package cscore.concurrent.java.threadpool;
public class TestPool {
public static void main(String[] args) throws InterruptedException {
ThreadPool pool = new ThreadPool(3, 1024);
for (int i = 0; i < 10; i++) {
int tmp = i;
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " say hello " + tmp);
});
}
pool.shutDown();
}
}
上面的代碼輸出結果:
ThreadPool-2-thread say hello 1
ThreadPool-2-thread say hello 3
ThreadPool-2-thread say hello 4
ThreadPool-2-thread say hello 5
ThreadPool-2-thread say hello 6
ThreadPool-2-thread say hello 7
ThreadPool-2-thread say hello 8
ThreadPool-2-thread say hello 9
ThreadPool-3-thread say hello 2
ThreadPool-1-thread say hello 0
從上面的結果來看確實實現(xiàn)了線程池的效果。
雜談
可能你會有疑問,當我們調用 interrupt的時候是如何產(chǎn)生異常的,我們仔細看一個阻塞隊列的實現(xiàn)。在ArrayBlockingQueue當中take方法實現(xiàn)如下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}在這個方法當中調用的是鎖的lock.lockInterruptibly();方法,當調用這個方法的時候線程是可以被interrupt方法中斷的,然后會拋出InterruptedException異常。
總結
在本篇文章當中我們主要實現(xiàn)了一個乞丐版的線程池,這個線程池離JDK給我們提供的線程池還是有一點距離,JDK給我們提供給的線程池還有很多其他的參數(shù),我們將在后續(xù)的幾篇文章當中繼續(xù)向JDK給我們提供的線程池靠近,直至實現(xiàn)一個盜版的JDK的線程池。本篇文章的代碼在下面的鏈接當中也可以訪問。
到此這篇關于Java實現(xiàn)手寫乞丐版線程池的示例代碼的文章就介紹到這了,更多相關Java線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot中使用@Transactional注解事物不生效的坑
這篇文章主要介紹了springboot中使用@Transactional注解事物不生效的原因,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01
springboot攔截器Interceptor的使用,你都了解嗎
springmvc 中的攔截器可以對請求進行判別,在請求到達控制器之前,把非法的請求給攔截掉下面來說一說, 它在springboot中的使用,感興趣的朋友一起看看吧2021-07-07

