在上一篇:玩轉Java線程池(2):Tomcat是如何修改創建線程的策略的?中,我介紹了 Tomcat 是如何去改變原來的JDK中的創建線程的過程的。從中,我們發現 Tomcat
爲了達到改變創建過程的目的,繼承原來的ThreadPoolExecutor
,重寫了 execute
方法,而且爲了配合重寫後的 execute
的方法的使用,還實現了一個新的阻塞隊列 TaskQueue
,二者配合使用,可以算是一種緊耦合的實現了。那麼有沒有一種比較簡單的實現?因爲在這個方案裏,我們需要一次性實現兩個新的類,還需要把二者配合使用。Tomcat
的實現略顯複雜,我們的使用往往追求簡單、高效,所以需要一種更加簡單的實線方式。
注意,這裏的實現的思路都來自這個回答:How to get the ThreadPoolExecutor to increase threads to max before queueing?。在這個回答的下面有很多不錯的思路。
1 阻塞隊列 和 拒絕策略 的 松耦合的實現
這裏的實現主要是來自這個回答
https://stackoverflow.com/questions/19528304/how-to-get-the-threadpoolexecutor-to-increase-threads-to-max-before-queueing/19528305#19528305
其實具體的思想還是和 Tomcat
的實現思想差不多,只不過做了刪減。
1.1 阻塞隊列的實現
阻塞隊列僅僅是重載了 offer
方法。
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
@Override
public boolean offer(Runnable e) {
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
如果這個是空的隊列,那麼就把任務進隊,如果此時的隊列是非空的,那麼就返回 false
。
爲什麼要保證隊列非空才把任務進隊?我推測是爲了保證這個時候的線程都是處於滿載的狀態
1 如果這個時候隊列裏面有一個元素,那麼,整個線程池就是沒有空閒的線程的。那麼創建新的工作線程就成了理所應當的事情了。
2 如果這個時候隊列裏面沒有元素的話,那麼很大概率此時的線程池是有空閒線程的,那麼你入隊,這個任務會馬上被工作線程消費。
1.2 拒絕策略
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
executor.getQueue().put(r);
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + executor);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
這個實現就簡單了,就是利用了LinkedBlockingQueue
de put()
的方法,這個方法的作用就是,在進行入隊操作的時候,如果隊列是滿的狀態,那麼就會繼續阻塞,直到隊列非空的時候,再進行如對操作。但是這個存在缺陷,就是這個 put()
沒有設置等待超時機制。這樣如果線程池的某些任務出現了差錯,就會導致線程池的提交任務出現阻塞。
所以如果不是那種重要的任務,允許丟失,或者,在拋出異常的時候有保存任務的機制,那麼就可以使用有超時機制的操作,比如用 boolean offer(E e, long timeout, TimeUnit unit)
這樣就能保證在一定時間之後,放棄入隊操作了。比如 在Tomcat 的實現的 阻塞隊列(TaskQueue) 實現中用的就是這個操作
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if ( parent==null || parent.isShutdown() )
throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
return super.offer(o,timeout,unit); // 進行如對操作。
}
1.3 完整代碼
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
@Override
public boolean offer(Runnable e) {
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/,
50 /*max*/,
60 /*secs*/,
TimeUnit.SECONDS,
queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
executor.getQueue().put(r);
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + executor);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});