最近看了java程式設計藝術之後,又看了一邊線程池源碼,發現自己很多瞎幾把用的地方 mark一下
對于線程池的參數了解,和線程池大小的設定
線程池的參數了解
平常工作通常就是
複制
new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
)
黏貼
new ThreadPoolExecutor(
0,
8,
30,
TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>()
}
0代表核心線程,8代表最大線程數量,30代表逾時時間,TimeUnit.MINUTES表示時間機關,new LinkedBlockingQueue() 代表隊列
一直這麼用也沒出問題。
一直到今天發現,沒出問題純粹是運氣好
把上面的代碼稍稍做出改變
new ThreadPoolExecutor(
0,
8,
30,
TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(10)
}
沒錯,就是在workQueue當中加一個10,RejectedExecutionException 分分鐘出現
為什麼那?????
第一個僥幸 在建立工作隊列時,使用了無參方法,預設使用了int的最大值建立工作隊列,是以這個隊列的最大容量可以達到2147483648 21億基本上的公司都達不到這個量 是以僥幸逃過
那麼為什麼 隊列容量縮小之後就立馬出現問題了呐?
這個時候我們就要看到線程池的建立代碼
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
在5個參數的構造方法下面,有調用了7參的方法,加入了預設的線程工廠和預設的攔截政策AbortPolicy,
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
java.util.concurrent.ThreadPoolExecutor 類中對攔截政策定義了四種模式
- CallerRunsPolicy :這個政策重試添加目前的任務,他會自動重複調用 execute() 方法,直到成功。
- AbortPolicy :對拒絕任務抛棄處理,并且抛出異常。
- DiscardPolicy :對拒絕任務直接無聲抛棄,沒有異常資訊。
- DiscardOldestPolicy :對拒絕任務不抛棄,而是抛棄隊列裡面等待最久的一個線程,然後把拒絕任務加到隊列。
是以當送出任務數超過maxmumPoolSize+workQueue之和時 也就是示例當中 8+10 超過19個任務的時候,
直接抛出異常 RejectedExecutionException
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
mark 擴充
仔細觀察這個類的實作,繼承 RejectedExecutionHandler 重寫 rejectedExecution,那麼是否可以自定義 攔截政策呐?
寫一個CustomRejectedExecutionHandler 類 繼承 RejectedExecutionHandler 重寫 rejectedExecution
private class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
do something
System.out.println("is come");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("error.............");
}
}
}
new ThreadPoolExecutor(
10,
30,
30,
TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new CustomRejectedExecutionHandler() );
此時,當我們隊列滿的時候,就會進入自己的拒絕政策
重寫工廠方法也是同理,繼承ThreadFactory 重寫 newThread方法
線程池的大小設定
通常的百度,都會告訴我們線程池的大小設定分為 io密集型和 cpu密集型,cpu密集型則,線程池大小為 cpu數量, io密集型則為 cpu*2+1
《java開發變成實戰》 中給出這樣一個公式
Ncpu = CPU的數量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待時間與計算時間的比率
為保持處理器達到期望的使用率,最優的池的大小等于:
Nthreads = Ncpu x Ucpu x (1 + W/C)
windos環境下 4核心8線程的電腦
寫了一段純cpu代碼
long sub=0;
for(long i=0;i<count;i++){
sub+=i;
}
count的值等于 1000000000L 時,單線程計算大約為500毫秒
count的值等于 10000000000L 時,單線程計算大約為2628毫秒

圖上隻是一小部分資料,實際測試更多次,得出以下結果
實測發現如下特點
1, 8個線程時,單個任務時間基本保持單線程差不多,約等于500毫秒,
100線程時,單個任務的時間上升到2000毫秒左右
說明頻繁的上下文切換存在。線程并不是越多越好
2,500ms任務執行數量上升時,100線程反而最快,當任務數量上漲時,優勢被正比擴大
,當線程時間大時,存在 線程數量=cpu數量的最優結果
得出結論,公式隻是一個參考值,切換和排程并不受控制,實際情況需要根據壓測結果設定。特别是當線程的cpu時間較短時。