service 方法註釋說明_JAVA執行緒池原理原始碼解析—為什麼啟動一個執行緒池,提交一個任務後,Main方法不會退出?...
阿新 • • 發佈:2020-12-25
技術標籤:service 方法註釋說明
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.submit(() -> System.out.println("Hello "));
System.out.println("World");
}
複製程式碼
呵呵,執行結果誰都知道,顯而易見
但是小老弟,有沒有發現這個程式一直都沒有結束
呢?明明這個任務都已經跑完了呀~
結論
開始了嗎?不好意思已經結束了,嘻嘻,大過年的不賣關子,我們直接公佈答案,造成不退出的原因是這樣:
- 你醜
- 執行緒池的建立的時候,第一次
submit
操作會建立Worker
執行緒(負責去拿任務處理),該執行緒裡寫了一個死迴圈,所以這個Worker
執行緒不會死 Worker
執行緒在建立的時候,被設定成了 非守護執行緒 ,thread.setDaemon(false)
- 早在
JDK1.5
的時候,就規定了當所有非守護執行緒退出時,JVM
才會退出,Main
方法主執行緒和Worker
執行緒都是非守護執行緒,所以不會死。
下面我們會就上面幾個問題,每一個問題進行原始碼分析,感興趣的看官老爺可以繼續,看看又不會掉髮(逃
原始碼分析
為什麼Worker執行緒不會死
夢開始的地方先從初始化開始
//該方法利用多臺例項化了一個ThreadPoolExecutor執行緒池,該執行緒池繼承了一個抽象類AbstractExecutorService ExecutorService service = Executors.newFixedThreadPool(10); //呼叫了ThreadPoolExecutor.submit方法也就是父類的AbstractExecutorService.submit,該方法內部會去呼叫execute()方法 service.submit(() -> System.out.println("Hello ")); 複製程式碼
於是我們定位到 ThreadPoolExecutor
類的 execute
方法,我截取了部分如下, 注意程式碼中我打註釋的地方
public void execute(Runnable command) {
...
//如果工作執行緒還沒有超過核心執行緒數
if (workerCountOf(c) < corePoolSize) {
//去新增工作執行緒
if (addWorker(command, true))
return;
}
...
複製程式碼
執行緒池把每一個執行任務的工作執行緒抽象成了 Worker
,我們定位到內部 addWorker
方法
...
//新建一個Worker
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()) // 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;
複製程式碼
這時候一個工作執行緒也就跑起來了,可以去執行任務了,我們定位到 ThreadPoolExecutor
的內部類 Worker
的 run
方法裡
//該類呼叫了runWorker方法
public void run() {
runWorker(this);
}
複製程式碼
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//主要看這個while,會看這個Worker有沒有任務,如果沒有就會去取,這裡是一個死迴圈,然後我們定位到getTask()方法,看他是怎麼取任務的
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
...
}
複製程式碼
這裡解釋了,工作執行緒其實不會死(超時時間不在本期範圍內),我們繼續定位到內部的 getTask()
方法,看他是怎麼取任務的
private Runnable getTask() {
...
//有沒有設定核心執行緒超時時間(預設沒有)當前工作的執行緒數大於了執行緒池的核心線城市
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
...
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//呼叫workQueue的Take方法,WorkQueue預設是一個BlockingQueue,所以呼叫take方法會導致當前工作執行緒阻塞掉,指到拿到
workQueue.take();
//如果拿到任務就返回
if (r != null)
return r;
timedOut = true;
...
複製程式碼
小結:
這裡想說的有兩點:
- 工作執行緒不會死(不設定執行緒存活時間,預設情況下),會一直拿任務,所以工作執行緒會一直活著
- 工作執行緒拿任務的時候,預設情況下,因為用的是
BlockingQueue
的take()
拿不到任務會阻塞
Worker執行緒如何被設定成非守護執行緒
首先我們來到 ThreadPoolExecutor
的構造方法裡
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
複製程式碼
構造器裡傳入了一個 ThreadFactory
也就是 Executors.defaultThreadFactory()
,用來產生工作執行緒,一步一步的點進去我們會定位到 Executors
內部類 DefaultThreadFactory
的 newThread
方法在此我向大家推薦一個架構學習交流裙。交流學習裙號:687810532,裡面會分享一些資深架構師錄製的視訊錄影
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
//關鍵程式碼是這裡,把執行緒設定成了非守護執行緒
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
複製程式碼
然後我們看 ThreadPoolExector
方法去 new Worker()的時候
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//這裡的ThreadPool,就是上面提到的那個生產非守護執行緒的執行緒工廠
this.thread = getThreadFactory().newThread(this);
}
複製程式碼
看上面的 註釋下面的內容 ,為什麼是非守護執行緒就真相大白了。