天天看點

java并發程式設計之 線程池的使用

最近看了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 類中對攔截政策定義了四種模式

  1. CallerRunsPolicy :這個政策重試添加目前的任務,他會自動重複調用 execute() 方法,直到成功。
  2. AbortPolicy :對拒絕任務抛棄處理,并且抛出異常。
  3. DiscardPolicy :對拒絕任務直接無聲抛棄,沒有異常資訊。
  4. 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毫秒

java并發程式設計之 線程池的使用

圖上隻是一小部分資料,實際測試更多次,得出以下結果

實測發現如下特點

1, 8個線程時,單個任務時間基本保持單線程差不多,約等于500毫秒,

100線程時,單個任務的時間上升到2000毫秒左右

說明頻繁的上下文切換存在。線程并不是越多越好

2,500ms任務執行數量上升時,100線程反而最快,當任務數量上漲時,優勢被正比擴大

,當線程時間大時,存在 線程數量=cpu數量的最優結果

得出結論,公式隻是一個參考值,切換和排程并不受控制,實際情況需要根據壓測結果設定。特别是當線程的cpu時間較短時。

繼續閱讀