線程池
java中的線程池有很多種,首先來看下其中最基礎的一種線程池ThreadPoolExecutor。線程池的作用爲,減少創建線程和銷燬線程的開銷,對線程進行復用。所以看源碼之前,提出以下問題:
- 在線程池中,空閒期,線程怎麼維持?
- 在使用時,線程池怎麼創建線程?
- 怎麼控制多個線程的併發
- 保存任務的載體是什麼。
ThreadPoolExecutor的屬性
關鍵屬性
//ctl這個屬性在後面的方法會經常出現,用法很類似ReentrantReadWriteLock使用一個變量來保存2個值.假設ctl是32位的2進製表示,則高3位,用來表示線程池狀態,後面29位來記錄線程池個數。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
首先來看下ThreadPoolExecutor的幾種狀態值
//該值表示掩碼個數
private static final int COUNT_BITS = Integer.SIZE - 3;
// runState is stored in the high-order bits
//接受新任務並且處理阻塞隊列裏的任務
private static final int RUNNING = -1 << COUNT_BITS;
//拒絕新任務,但是處理阻塞隊列裏面的任務
private static final int SHUTDOWN = 0 << COUNT_BITS;
//拒絕新任務,拋棄阻塞隊列,同時終端正在處理的任務
private static final int STOP = 1 << COUNT_BITS;
// 所有任務都執行完後,當前線程活動線程數爲0 將要調用terminated方法,
private static final int TIDYING = 2 << COUNT_BITS;
//終止狀態,terminated方法後的狀態
private static final int TERMINATED = 3 << COUNT_BITS;
在ThreadPoolExecutor後續的方法中,都會根據上訴的各種狀態來進行判斷。
//阻塞隊列
private final BlockingQueue<Runnable> workQueue;
//線程池中的獨佔鎖,保證在多線程的情況下的線程安全
private final ReentrantLock mainLock = new ReentrantLock();
//Worker是具體承載任務的對象 ,可以理解爲把一個thread和runnable組成一個對象,並使用其中的thread去執行。--在下面具體分析。
private final HashSet<Worker> workers = new HashSet<Worker>();
// 線程池終止的條件隊列
private final Condition termination = mainLock.newCondition();
//飽和策略,在ThreadPoolExecutor中有幾個內部類來實現不同策略
private volatile RejectedExecutionHandler handler;
//創建線程的工廠
private volatile ThreadFactory threadFactory;
//核心線程數,也就是當沒有任務需要執行時,會維持corePoolSize個的線程
private volatile int corePoolSize;
//線程池中的最大線程數
private volatile int maximumPoolSize;
...
以上是ThreadPoolExecutor 的幾個核心的點,當然還有好幾個重要的屬性,後面分析。
根據當前的屬性和之前所看過的知識。可以得到以下圖片:
也就是說,相當於是一個消費者生產者的模型。由用戶提交任務到線程池中,線程池就開始消費。
拒絕策略如下
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
//當阻塞隊列中的任務滿了,並且線程數也到達最大,則拋出異常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
//默默拋棄了,不作處理
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
//從隊列中拋棄一個任務,把當前的任務加入到隊列中
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
//使用當前調用者的線程,來執行任務
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
構造方法
取其中一個參數最多構造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
可以看到各種屬性都可以自定義。同時Executors
還提供了幾種方便的線程池,如下:
//固定大小線程池
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
//線程只有一個
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
//阻塞隊列只有一個任務
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
當然在平常操作中,不建議使用這一的方式去創建,不夠靈活。
普通方法
- 提交任務
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//獲得ctl值
int c = ctl.get();
//判斷現在的線程是否少於核心線程數
if (workerCountOf(c) < corePoolSize) {
//少的話就,直接創建新的線程
if (addWorker(command, true))
return;
c = ctl.get();
}
//看線程池是否是運行狀態,並且添加任務到阻塞隊列
if (isRunning(c) && workQueue.offer(command)) {
//2次檢查,判端是否還是處於運行狀態
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
//如果依舊處於運行狀態
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
//線程池狀態變化了,直接調用拒絕策略
reject(command);
}
addWorker 這個方法的內容有一點長,總體可以分爲2步。
- 對ctl這個變量進行修改,使線程數+1
- 把任務打包成一個woker,並啓動線程
private boolean addWorker(Runnable firstTask, boolean core) {
// 對狀態值進行修改 ,主要的方式是cas進行修改值
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//這個判斷可以分爲以下幾種情況會使方法直接返回false
//1 狀態值大於等於 SHUTDOWN
//2 狀態值爲shutdown並阻塞隊列爲空
//3 狀態值爲shutdown 並且傳入的firsttask爲null
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;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
}
}
//把任務打包成worker放入workers中運行,使用了ThreadPoolExecutor中的獨佔鎖,對整個添加過程加鎖。
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//獲取加鎖後的線程池狀態
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
//加入workers
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//執行本次任務
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
到這裏我們可以看到的是,當線程池的線程少於最大線程時,會創建一個線程取執行任務。而已經到達最大線程數時,會把任務加入阻塞隊列。那麼問題時,阻塞隊列中的任務何時會被調用?下面會分析。
- submit
在線程池中,submit方法與execute方法2者,作用相似,但是submit方法可以返回一個Future的對象,通過對象的get方法可以得到執行的結果。代碼如下:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
可以看到,在submit中,先吧任務打包成future,然後再調用execute方法去執行。
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
通過future的get方法,可以獲得線程的運行結果,同時也可以保證線程的安全。
- Worker 內部類
用戶線程把任務提交到線程後,由worker來執行。
//Worker 的信息,繼承了aqs。前面 講到aqs是鎖的底層實現
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
// ---------------
Worker(Runnable firstTask) {
setState(-1); // 這裏設置爲-1,可以避免在調用runworker方法之前,被中斷
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);//創建一個線程
}
runworker 也就是去運行線程的方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 把state設置爲0,允許中斷
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();//在這裏加鎖的原因是,爲了防止,在任務運行期間,其他有線程調用了shutdown。中斷任務
//判斷當前的執行狀態是否合理
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//執行任務
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
//統計當前worker完成了多少任務
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//清理worker
processWorkerExit(w, completedAbruptly);
}
}
在上訴代碼中可以看到循環的條件while (task != null || (task = getTask()) != null)
當任務隊列中,不存在任務時,纔會退出循環。這點與消費者生產者的數據模型,非常相似。也是說明了,當有任務存在時,會一直進行循環,觸發任務。
processWorkerExit 清理工作
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//統計到整個線程池完成任務的個數
completedTaskCount += w.completedTasks;
//從workers中移除當前的worker
workers.remove(w);
} finally {
mainLock.unlock();
}
//嘗試設置終止狀態,如下2中情況,會變成終止狀態
//1.shutdown並且工作隊列爲空
//2、stop,並且沒有活動線程存在
tryTerminate();
//把線程個數維持在coresize
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
以上代碼可以得出結論,線程池中線程個數,是按worker的個數來計算的。
shutdown 方法,調用以後線程池不會再接受新的任務,但是工作隊列中的任務還是要執行的。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//權限檢查
checkShutdownAccess();
//設置當前線程池狀態爲shutdown,如果已經是shutdown則直接返回
advanceRunState(SHUTDOWN);
//設置中斷標記
interruptIdleWorkers();
onShutdown(); // ScheduledThreadPoolExecutor 的鉤子,下一篇會分析ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//嘗試吧線程池狀態改爲終止
tryTerminate();
}
interruptIdleWorkers
//入參可以選擇是否只對一個wokrer中斷
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//獲取workre的鎖
if (!t.isInterrupted() && w.tryLock()) {
try {
//設置中斷
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
上訴方法中,可以看到,設置中斷之前,需要獲得worker裏面的鎖,由於在上面的runworkre中可以看到,在執行任務期間會獲取鎖,所以這裏不會中斷,正在執行任務的線程。
shutdownNow 操作是更近一步的操作,直接丟棄工作隊列中的任務,並當前執行中的任務也會被中斷。返回值爲被丟棄的任務
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
設置中斷標識
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
很明顯,這裏的中斷線程,並沒有獲取當前worker的鎖,而是直接中斷。
總結
線程池就是這樣的一個類,採用了一個原子變量來記錄狀態和線程個數。3位+29位這樣的一個組合。同時利用wokre來包裝線程,處理多個任務,減少的線程的創建和開銷。當然這只是一部分的線程池內容,後續在scheduledThreadPoolExecutor,分析另一部分的內容。