Java中ExecutorService和ThreadPoolExecutor運行原理
為什么要使用線程池
服務(wù)器應(yīng)用程序中經(jīng)常出現(xiàn)的情況是:單個任務(wù)處理的時間很短而請求的數(shù)目卻是巨大的。
構(gòu)建服務(wù)器應(yīng)用程序的一個過于簡單的模型應(yīng)該是:每當一個請求到達就創(chuàng)建一個新線程,然后在新線程中為請求服務(wù)。實際上,對于原型開發(fā)這種方法工作得很好,但如果試圖部署以這種方式運行的服務(wù)器應(yīng)用程序,那么這種方法的嚴重不足就很明顯。
每個請求對應(yīng)一個線程(thread-per-request)方法的不足之一是:為每個請求創(chuàng)建一個新線程的開銷很大;為每個請求創(chuàng)建新線程的服務(wù)器在創(chuàng)建和銷毀線程上花費的時間和消耗的系統(tǒng)資源要比花在處理實際的用戶請求的時間和資源更多。除了創(chuàng)建和銷毀線程的開銷之外,活動的線程也消耗系統(tǒng)資源(線程的生命周期!)。在一個JVM 里創(chuàng)建太多的線程可能會導致系統(tǒng)由于過度消耗內(nèi)存而用完內(nèi)存或“切換過度”。為了防止資源不足,服務(wù)器應(yīng)用程序需要一些辦法來限制任何給定時刻處理的請求數(shù)目。
線程池為線程生命周期開銷問題和資源不足問題提供了解決方案。通過對多個任務(wù)重用線程,線程創(chuàng)建的開銷被分攤到了多個任務(wù)上。其好處是,因為在請求到達時線程已經(jīng)存在,所以無意中也消除了線程創(chuàng)建所帶來的延遲。這樣,就可以立即為請求服務(wù),使應(yīng)用程序響應(yīng)更快。而且,通過適當?shù)卣{(diào)整線程池中的線程數(shù)目,也就是當請求的數(shù)目超過某個閾值時,就強制其它任何新到的請求一直等待,直到獲得一個線程來處理為止,從而可以防止資源不足。
線程池的創(chuàng)建
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-name").build();
//創(chuàng)建線程池
ExecutorService exc = new ThreadPoolExecutor(20, 20, 30000,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), namedThreadFactory);
/*
參數(shù)的意義
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
*/
線程的提交方法
在idea中,我們可以通過alt+7(注意不是F7)在左下角查看當前類的所有方法

在圖中我們可以看到ExecutorService有execute和submit兩種方法,但是他并沒有實現(xiàn)execute方法,所以方法是灰的
接下來我們看ExecutorService的實現(xiàn)類ThreadPoolExecutor

可以看到ThreadPoolExecutor 實現(xiàn)了execute這個方法,那接下來我們具體看execute和submit的方法
具體實現(xiàn)
在我第一次學習時,我就是這么簡單來理解的
exc.submit() //提交有返回值 傳入的為callable和runable 返回值為future exc.execute() //提交無返回值 傳入的為runable
但是我發(fā)現(xiàn)為什么submit可以執(zhí)行callable,又執(zhí)行runable?這不是兩個不同的創(chuàng)建線程的方式嗎?我點進去了ThreadPoolExecutor類,但是在其中沒有找到submit方法,于是我按了alt+7

在AbstractExecutorService類中發(fā)現(xiàn)了submit方法
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
在代碼中我們可以看到,三個方法都是用這個代碼來統(tǒng)一實現(xiàn)的,
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
不同的是,當使用submit而傳入的是runable接口時,會多一個返回值的參數(shù),如果沒有這個參數(shù)則會在newTaskFor中多加一個null參數(shù),我們再進入newTaskFor方法
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
同樣存在兩個方法,一個是為了接收runable,一個接收callable,但是這次都是使用了new FutureTask來傳入,因為FutureTask可以運行二者
我們知道Thread實現(xiàn)了Runable接口可以實現(xiàn)線程,但關(guān)于為什么FutureTask可以運行Callable接口
首先,在下面代碼中可以看到FutureTask實現(xiàn)了RunnableFuture,RunnableFuture實現(xiàn)了Runnable,所以可以用過Thread來運行start
public class FutureTask<V> implements RunnableFuture<V> public interface RunnableFuture<V> extends Runnable, Future<V> FutureTask<Integer> futureTask = new FutureTask(callableTask); Thread thread = new Thread(futureTask); thread.start();
而Thread中的start又是調(diào)用的接口中的run方法,但是Callable明明沒有run方法啊,這就要看FutureTask中的run方法了

在這個FutureTask的run方法中,調(diào)用的是Callable的call方法,所以得以運行,并且將返回值另外保存了,關(guān)于異步返回值的原理下次再說。
回到new FutureTask(callable);的代碼中
我們接著點進去看源碼
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
對于第一個傳入callable的我們已經(jīng)知道了原理,就是FutureTask如何通過Thread運行Callable的。
那么對于第二個傳入Runable的代碼又是個什么東西?
this.callable = Executors.callable(runnable, result);
好,我們再點進去看源碼,進入了一個Executors,這個類沒有任何的實現(xiàn)與繼承,真是太好了,看方法
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
哦豁,又將Runable傳入了一個RunnableAdapter類,真復雜,就要看看到底多少層,看這個名字,Adapter,適配器?大概知道里面會有什么操作了,我們再點進去看源碼
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
這是Executors中的一個內(nèi)部類,它實現(xiàn)了Callable接口,我依稀記得傳入的是一個Runable接口,原來在這個類中,將Callable的call方法重寫了,其中調(diào)用了Runable的run方法,并且具有返回值,還記得這個result嗎,在最初的AbstractExecutorService中,對于傳入Runable的submit方法有兩個,有參數(shù)則傳遞,無參數(shù)則傳入為null,

總結(jié)1
至此,就了解到了為什么submit又可以傳入Runable也可以傳入Callable。
接下來稍微說一下如何運行。
ThreadPoolExecutor運行原理

細心的小伙伴發(fā)現(xiàn)了,說好的submit,竟然又用execute
進入execute方法,直接用ctrl點擊無效,我們打開ThreadPoolExecutor中,按alt+7,左下角execute是亮的,方法是在這里實現(xiàn)的
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
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);
}
else if (!addWorker(command, false))
reject(command);
}
先是判斷線程池數(shù)量,在判斷傳入線程狀態(tài),滿足條件就使用addWorker,不滿足就reject拒絕
進入addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
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;
// else CAS failed due to workerCount change; retry inner loop
}
}
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 {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
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;
}
這里又是一堆條件驗證,都是核心代碼,最后通過一個內(nèi)置類worker來獲取線程實例,然后加鎖繼續(xù)驗證,條件都滿足時,t.start(),終于可以運行
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
---
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
---
workerAdded = true;
---
if (workerAdded) {
t.start();
---
總結(jié)2
至此,整個流程都已說明,關(guān)于Callable和Future完成異步返回值的原理,下次再說
到此這篇關(guān)于Java中ExecutorService和ThreadPoolExecutor運行原理的文章就介紹到這了,更多相關(guān)Java ExecutorService ThreadPoolExecutor內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java調(diào)用webservice的.asmx接口的使用步驟
- Java調(diào)用WebService接口作測試
- JAVA調(diào)用SAP WEBSERVICE服務(wù)實現(xiàn)流程圖解
- JAVA WSIMPORT生成WEBSERVICE客戶端401認證過程圖解
- Java 使用Axis調(diào)用WebService的示例代碼
- java使用xfire搭建webservice服務(wù)的過程詳解
- Java使用ExecutorService來停止線程服務(wù)
- Java解析調(diào)用webservice服務(wù)的返回XML串詳解
- 在spring boot中使用java線程池ExecutorService的講解
- java 出現(xiàn)NullPointerException的原因及解決辦法
- Java中避免NullPointerException的方法總結(jié)
- Java xml出現(xiàn)錯誤 javax.xml.transform.TransformerException: java.lang.NullPointerException
- Zend Studio for Eclipse的java.lang.NullPointerException錯誤的解決方法
- java靜態(tài)工具類注入service出現(xiàn)NullPointerException異常處理
相關(guān)文章
詳解Java編程中統(tǒng)一資源定位符URL的相關(guān)使用
這篇文章主要介紹了Java編程中統(tǒng)一資源定位符URL的相關(guān)使用,是Java網(wǎng)絡(luò)編程中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10
Java 如何實現(xiàn)POST(x-www-form-urlencoded)請求
這篇文章主要介紹了Java 實現(xiàn)POST(x-www-form-urlencoded)請求,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Java8 中使用Stream 讓List 轉(zhuǎn) Map使用問題小結(jié)
這篇文章主要介紹了Java8 中使用Stream 讓List 轉(zhuǎn) Map使用總結(jié),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-06-06
springboot結(jié)合redis實現(xiàn)搜索欄熱搜功能及文字過濾
本文主要介紹了springboot結(jié)合redis實現(xiàn)搜索欄熱搜功能及文字過濾,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
關(guān)于Java中的實體類要?implements?Serializable的原因分析
這篇文章主要介紹了Java中的實體類為什么要?implements?Serializable,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06
spring AOP自定義注解方式實現(xiàn)日志管理的實例講解
下面小編就為大家分享一篇spring AOP自定義注解方式實現(xiàn)日志管理的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01

