線程池
線程池顧名思義,就是一個放置線程的池子。就跟數據庫連接池差不多。線程池通過對併發線程的控制,能有效的節省系統資源的浪費,提高系統的性能。
學習線程池,先了解一下線程池的一個基本結構:
Executor
public interface Executor {
void execute(Runnable command);
}
Executor是一個接口,其中只有一個方法,就是execute方法。所以Executor實際就是一個線程的執行者。
這裏就不把子類的所有方法全部列出來,全部學習一遍,大體可以參照API進行學習。這裏主要學習線程池ThreadPoolExecutor方法開始學習,來了解學習線程池的一個運行原理。
ThreadPoolExecutor的一些重要屬性
//ctl用來表示線程池的運行狀態,和線程池中任務的數量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//進制位。最大整數轉換成二進制的位數
private static final int COUNT_BITS = Integer.SIZE - 3;
//線程池的最大容量2的29次方減1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
。
//運行狀態,能接收任務,並對已經添加的任務進行處理
private static final int RUNNING = -1 << COUNT_BITS;
//不能接收新任務,但能處理已經添加的任務
private static final int SHUTDOWN = 0 << COUNT_BITS;
//不能接收新任務,也不能處理已經添加的任務,並且中斷已經在處理的任務
private static final int STOP = 1 << COUNT_BITS;
//當所有的任務已終止,ctl記錄的"任務數量"爲0,線程池會變爲TIDYING狀態。當線程池變爲TIDYING狀態時,會執行鉤子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變爲TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。
private static final int TIDYING = 2 << COUNT_BITS;
//線程池徹底終止,就變成TERMINATED狀態。
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking 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; }
//線程的阻塞隊列
private final BlockingQueue<Runnable> workQueue;
//互斥鎖
private final ReentrantLock mainLock = new ReentrantLock();
//線程工作集
private final HashSet<Worker> workers = new HashSet<Worker>();
//終止條件
private final Condition termination = mainLock.newCondition();
// 線程池中線程數量曾經達到過的最大值。
private int largestPoolSize;
// 已完成任務數量
private long completedTaskCount;
//線程工廠
private volatile ThreadFactory threadFactory;
//線程被拒絕時的處理策略
private volatile RejectedExecutionHandler handler;
// 保持線程存活時間。
private volatile long keepAliveTime;
//是否允許"線程在空閒狀態時,仍然能夠存活
private volatile boolean allowCoreThreadTimeOut;
//核心線程池大小
private volatile int corePoolSize;
//線程池的最大容量
private volatile int maximumPoolSize;
這麼多變量,可能要記住也不是很容易。通過分析Execute方法可以更加好的理解工作原理和這些屬性的意義
public void execute(Runnable command) {
//執行的線程爲空拋出空指針異常。
if (command == null)
throw new NullPointerException();
//這裏整體可以分三步。
//1. 如果當前線程池中任務數量(一個任務可以理解就是一個線程)小於中心池的容量(corePoolSize),就直接爲command新建一個線程,加入任務集workers,並啓動該線程。
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.如果當前任務數量大於了corePoolSize,並且線程池是可運行狀態。就把任務加入到任務隊列workQueue中。 加入隊列之後,再次確認線程池的狀態,這個時候狀態不是可運行的,那就把任務從隊列中刪除,並嘗試終止線程池。如果是可運行,那麼就檢查線程池中工作數量是否爲0,如果沒有了,那麼就添加一個任務爲空的線程。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//3.如果任務數量大於中心池數量,添加對了也失敗了(這裏隊列是BlockingQueue,前面學習過隊列有有界隊列和無界隊列,所以有可能隊列滿了導致添加失敗。活着其它原因),那麼就再進行一次嘗試添加到任務集中去,如果失敗,執行拒絕策略。
else if (!addWorker(command, false))
reject(command);
}
通過上面的步驟學習,可以大致的理一下思路,線程池,有一箇中心池容量,這個容量沒有滿,就可以直接添加任務運行,而任務是被 添加到一個HashSet的Worker中。如果滿了,就把任務添加到一個BlockingQueue隊列中。都失敗了,就直接運行一個拒絕策略。所以,就要理解三個東西:
- 工作集。
- 任務隊列
- 拒絕策略。
理解了這三個東西,那麼大致就可以瞭解線程池的一個基本原理。
工作集 Worker
//Worker是線程池的一個內部類 集成了AQS,實現了Runnable接口。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker 運行前,不準中斷
this.firstTask = firstTask; //初始任務值
this.thread = getThreadFactory().newThread(this);//通過線程工爲當前任務創建一個線程
}
public void run() {
runWorker(this);//運行
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
工作集的就是把任務通過線程工廠創建一個該任務的線程並運行。
* 任務隊列*
從定義上看我們知道任務隊列是一個BlockingQueue。所以線程池中的任務隊列可以是任意BlockingQueue的子類。但是常用線程池中常用的的是ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue .下一節會學習重用的線程池類型。
拒絕策略
AbortPolicy – 當任務添加到線程池中被拒絕時,它將拋出 RejectedExecutionException 異常。
CallerRunsPolicy – 當任務添加到線程池中被拒絕時,會在線程池當前正在運行的Thread線程池中處理被拒絕的任務。
DiscardOldestPolicy – 當任務添加到線程池中被拒絕時,線程池會放棄等待隊列中最舊的未處理任務,然後將被拒絕的任務添加到等待隊列中。
DiscardPolicy – 當任務添加到線程池中被拒絕時,線程池將丟棄被拒絕的任務。
大致瞭解學習了線程池的一個主要運行過程和基本原理。下一節將會學習JDK自帶的幾種線程池,更加進一步學習和理解線程池。