java使用默認線程池踩過的坑(二)

雲智慧(北京)科技有限公司 陳鑫

是的,一個線程不能夠啓動兩次。那麼它是怎麼判斷的呢?
public synchronized void start() {
/**
* A zero status valuecorresponds to state “NEW”. 0對應的是state NEW
*/
if (threadStatus!= 0) //如果不是NEW state,就直接拋出異常!
throw newIllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0(); // 啓動線程的native方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch(Throwable ignore) {
}
}
}
恩,只有是NEW狀態才能夠調用native方法啓動一個線程。好吧,到這裏了,就普及也自補一下jvm裏的線程狀態:
所有的線程狀態::
l NEW —— 還沒有啓動過
l RUNNABLE —— 正在jvm上運行着
l BLOCKED —— 正在等待鎖/信號量被釋放
l WAITING —— 等待其他某個線程的某個特定動作
l TIMED_WAITING —— A thread that iswaiting for another thread to perform an action for up to a specified waitingtime is in this state.
l TERMINATED —— 退出,停止
線程在某個時間點上只可能存在一種狀態,這些狀態是jvm裏的,並不反映操作系統線程的狀態。查一下Thread的API,沒有對其狀態進行修改的API。那麼這條路是不通的嗎?
仔細考慮一下……
如果把任務做成Runnable實現類,然後在把這個實現類丟進線程池調度器之前,利用此Runnable構造一個Thread,是不是這個Thread對象就能夠控制這個runnable對象,進而控制在線程池中運行着的task了呢?非也!讓我們看看Thread和ThreadPoolExecutor對Runnable的處理吧。
Thread
/* What will berun. */
private Runnabletarget;
結合上面的start()方法,很容易猜出,start0()會把target弄成一個線程來進行運行。
ThreadPoolExecutor
public void execute(Runnable command){
if (command== null)
thrownew NullPointerException();
int c =ctl.get();
if(workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c =ctl.get();
}
if(isRunning(c) && workQueue.offer(command)) {
intrecheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if(workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

private boolean addWorker(RunnablefirstTask, boolean core) {

booleanworkerStarted = false;
booleanworkerAdded = false;
Worker w =null;
try {
finalReentrantLock mainLock = this.mainLock;
w = newWorker(firstTask);
finalThread t = w.thread;
if (t!= null) {
mainLock.lock();
try{
int c = ctl.get();
int rs = runStateOf(c);

               if (rs < SHUTDOWN ||
                   (rs == SHUTDOWN && firstTask == null)) {
                   if (t.isAlive()) // precheck that t is startable
                        throw newIllegalThreadStateException();

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;
}
那麼Worker又是怎樣的呢?
Worker
private final class Worker
extendsAbstractQueuedSynchronizer
implementsRunnable
{
finalThread thread;
RunnablefirstTask;
volatilelong completedTasks;
Worker(Runnable firstTask) {
setState(-1); //調用runWorker之前不可以interrupt
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public voidrun() {
runWorker(this);
}
……
…….
voidinterruptIfStarted() {
Threadt;
if(getState() >= 0 && (t = thread) != null &&!t.isInterrupted()) {
try{
t.interrupt();
}catch (SecurityException ignore) {
}
}
}
}
可見worker裏既包裝了Runnable對象——task,又包裝了一個Thread對象——以自己作爲初始化參數,因爲worker也是Runnable對象。然後對外提供了運行與停止接口,run()和interruptIfStarted()。回顧上面使用Thread的例子不禁有了新的領悟,我們把一個Thread對象交給ThreadPoolExecutor執行後,實際的調用是對Thread(FileTask())對象,我們暫時稱之爲workerWrapper。那麼我們在池外進行FileTask.interrupt()操作影響的是FileTask對象,而不是workerWrapper。所以可能上面對於start()方法二次調用不是特別適當。更恰當的應該是在fileTask.interrupt()的時候就跑出異常,因爲從來沒有對fileTask對象執行過start()方法,這時候去interrupt就會出現錯誤。具體如下圖:
無
分析到此,我們已經明確除了調用ThreadPoolExecutor了的interruptWorkers()方法別無其他途徑操作這些worker了。
private void interruptWorkers() {
finalReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for(Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}

發佈了44 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章