自定義線程池:
public class ThreadPoolTest {
public static void main(String[] args) {
//創建等待隊列
BlockingQueue<Runnable> bqueue= new ArrayBlockingQueue<Runnable>(20);
//創建線程池,池中保存的線程數爲3,允許的最大線程數爲5
ThreadPoolExecutor pool=new ThreadPoolExecutor(3,5,50, TimeUnit.MILLISECONDS,bqueue);
Runnable t1=new MyThread();
Runnable t2=new MyThread();
Runnable t3=new MyThread();
Runnable t4=new MyThread();
Runnable t5=new MyThread();
Runnable t6=new MyThread();
Runnable t7=new MyThread();
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
pool.execute(t7);
//關閉線程池
pool.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在執行。。。");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
七個任務是在線程池的三個線程上執行的。說明下用到的ThreadPoolExecuror類的構造方法中各個參數的含義:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue) {
}
corePoolSize:線程池中所保存的核心線程數,包括空閒線程。
maximumPoolSize:池中允許的最大線程數。
keepAliveTime:線程池中的空閒線程所能持續的最長時間。
unit:持續時間的單位。
workQueue:任務執行前保存任務的隊列,僅保存由execute方法提交的Runnable任務。
線程池execute()
工作的邏輯描述如下:
- 如果當前運行的線程,少於corePoolSize,則創建一個新的線程來執行任務。
- 如果運行的線程等於或多於 corePoolSize,將任務加入 BlockingQueue。
- 如果 BlockingQueue 內的任務超過上限,則創建新的線程來處理任務。
- 如果創建的線程數是單錢運行的線程超出 maximumPoolSize,任務將被拒絕策略拒絕。
在瞭解 execute() 方法之前,需要了解一下 clt
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
- 線程池的ctl是一個原子的 AtomicInteger。
- 這個ctl包含兩個參數 :(1) workerCount 激活的線程數; (2) runState 當前線程池的狀態;
- 它的低29位用於存放當前的線程數, 因此一個線程池在理論上最大的線程數是 536870911; 高 3 位是用於表示當前線程池的狀態, 其中高三位的值和狀態對應如下:
高三位 | 狀態 | 描述 |
---|---|---|
111 | RUNNING | 接受新任務並處理排隊的任務 |
000 | SHUTDOWN | 不接受新任務,但處理排隊的任務 |
001 | STOP |
不接受新任務,不處理排隊的任務,中斷正在進行的任務 |
010 | TIDYING |
所有任務都已終止,workerCount爲零,線程轉換狀態爲TIDYING,將運行terminated()鉤子方法 |
110 | TERMINATED | TERMINATED()已執行完成 |
爲了能夠使用 ctl 線程池提供了三個方法:
//獲取線程池的狀態
private static int runStateOf(int c) { return c & ~CAPACITY; }
//獲取線程池的工作線程數
private static int workerCountOf(int c) { return c & CAPACITY; }
//根據工作線程數和線程池狀態獲取 ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
execute()
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果工作線程數小於核心線程數,
if (workerCountOf(c) < corePoolSize) {
//執行addWork,提交爲核心線程,提交成功return。提交失敗重新獲取ctl
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果工作線程數大於等於核心線程數,則檢查線程池狀態是否是正在運行,且將新線程向阻塞隊列提交。
if (isRunning(c) && workQueue.offer(command)) {
//recheck 需要再次檢查,主要目的是判斷加入到阻塞隊裏中的線程是否可以被執行
int recheck = ctl.get();
//如果線程池狀態不爲running,將任務從阻塞隊列裏面移除,啓用拒絕策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果線程池的工作線程爲零,則調用addWoker提交任務
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//添加非核心線程失敗,拒絕
else if (!addWorker(command, false))
reject(command);
}
addWorker()
private static final int COUNT_BITS = Integer.SIZE - 3; //32-3
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 2的29次方-1
//線程池最大大小。注意,實際最大值在內部以CAPACITY爲界限。
private volatile int maximumPoolSize;
/**
* core:是否核心線程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (; ; ) {
int c = ctl.get(); //ctl的值
int rs = runStateOf(c);//線程池的狀態
//判斷是否可以添加任務
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
for (; ; ) {
int wc = workerCountOf(c); //工作線程數
//是否大於最大值,是否大於核心線程數、是否大於最大線程數
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS增加工作線程數
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
//如果線程池狀態改變,回到開始重新來
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
//上面的邏輯是考慮是否能夠添加線程,如果可以就cas的增加工作線程數量
//下面正式啓動線程
try {
//新建worker
w = new Worker(firstTask);
//獲取當前線程
final Thread t = w.thread;
if (t != null) {
//獲取可重入鎖
final ReentrantLock mainLock = this.mainLock;
//鎖住
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN ==> 線程處於RUNNING狀態
// 或者線程處於SHUTDOWN狀態,且firstTask == null(可能是workQueue中仍有未執行完成的任務,創建沒有初始任務的worker線程執行)
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 當前線程已經啓動,拋出異常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//workers 是一個 HashSet 必須在 lock的情況下操作。
workers.add(w);
int s = workers.size();
//設置 largeestPoolSize 標記workAdded
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//如果添加成功,啓動線程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//啓動線程失敗,回滾。
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
execute() 中有三處調用了 addWork()
我們逐一分析。
- 第一次,條件
if (workerCountOf(c) < corePoolSize)
這個很好理解,工作線程數少於核心線程數,提交任務。所以addWorker(command, true)
。 - 第二次,如果
workerCountOf(recheck) == 0
如果worker的數量爲0,那就addWorker(null,false)
。爲什麼這裏是null
?之前已經把 command 提交到阻塞隊列了workQueue.offer(command)
。所以提交一個空線程,直接從阻塞隊列裏面取就可以了。 - 第三次,如果線程池沒有 RUNNING 或者 offer 阻塞隊列失敗,
addWorker(command,false)
,很好理解,對應的就是,阻塞隊列滿了,將任務提交到,非核心線程池。與最大線程池比較。
重新歸納execute()
的邏輯應該是:
- 如果當前運行的線程,少於corePoolSize,則創建一個新的線程來執行任務。
- 如果運行的線程等於或多於 corePoolSize,將任務加入 BlockingQueue。
- 如果加入 BlockingQueue 成功,需要二次檢查線程池的狀態如果線程池沒有處於 Running,則從 BlockingQueue 移除任務,啓動拒絕策略。
- 如果線程池處於 Running狀態,則檢查工作線程(worker)是否爲0。如果爲0,則創建新的線程來處理任務。如果啓動線程數大於maximumPoolSize,任務將被拒絕策略拒絕。
- 如果加入 BlockingQueue 。失敗,則創建新的線程來處理任務。
- 如果啓動線程數大於maximumPoolSize,任務將被拒絕策略拒絕。
參考地址:https://www.xilidou.com/2018/02/09/thread-corepoolsize/