Java線程池總結和使用

線程池介紹

在實際開發中是極不推薦每次都手動去建立一個線程執行任務,由於每次都建立一個新的線程會形成很大的開銷。因此線程池的做用就是把建立好的線程存起來進行復用,每次任務都由線程池中的這些線程就行調用,這樣不只避免了重複建立帶來的開銷,也避免了建立太多的線程直接形成系統卡死。java


線程池使用

// 線程池初始化須要的參數
Integer corePoolSize = 1;
Integer maximumPoolSize = 2;
Integer keepAliveTime = 1;
TimeUnit unit = TimeUnit.SECONDS;
ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1);
ThreadFactory factory = Executors.defaultThreadFactory();
// 拒絕策略
RejectedExecutionHandler abortHandler = new ThreadPoolExecutor.AbortPolicy();
RejectedExecutionHandler CallerRunsHandler = new ThreadPoolExecutor.CallerRunsPolicy();
RejectedExecutionHandler DiscardOldestHandler = new ThreadPoolExecutor.DiscardOldestPolicy();
RejectedExecutionHandler DiscardHandler = new ThreadPoolExecutor.DiscardPolicy();

// 建立線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue, factory, abortHandler);
  1. 參數ide

    • corePoolSize(核心工做線程數)

      這個參數表示線程池中工做線程數量,若是設置了10就表示能夠同時有10個線程工做,即便10個線程中只有5個工做,其餘5個是空閒的,線程池也會維持這個數量的工做線程。性能

    • maximumPoolSize(最大線程數)

      這個參數是表示線程池中最大線程數量,由於實際中不可能你設置了10個工做線程恰好就是有10個任務進來,多是20個任務,這時候就會建立20個線程,有10個是工做線程,另外10個線程則等待執行。this

    • keepAliveTime(多餘線程存活時間)

      這個參數則是指定了上面說的除工做線程以外多餘等待的線程的存活時間,能夠設置過了多長時間就把這些等待的線程清理掉。線程

    • unit(時間單位)

      接受一個TimeUnit類型的參數,表示上面keepAliveTime參數的時間單位是多少。code

    • workQueue(隊列)

      全部的排隊等待空閒線程都放在這個隊列中。對象

    • factory(線程建立工廠)

      線程池使用這個工廠來建立線程。隊列

    • abortHandler(拒絕策略)

      上面講了工做線程數和最大線程數,可是實際中任務是可能比最大線程數還要大的,這個時候線程池就會根據你的拒絕策略來處理這些多餘的任務了,總共有四種策略,後續會介紹。開發

  2. 代碼示例get

    • 建立一個線程池

      這裏建立了一個工做線程數爲1,最大線程數爲2,空閒線程存活時間10秒,隊列長度爲1,拒絕策略是ThreadPoolExecutor.AbortPolicy(),就是直接放棄任務並拋出個異常

      // 工做線程數爲1
       Integer corePoolSize = 1;
      // 最大線程數爲2
       Integer maximumPoolSize = 2;
      // 空閒線程存活時間10秒
       Integer keepAliveTime = 10;
       TimeUnit unit = TimeUnit.SECONDS;
      // 隊列 
       ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1);
       ThreadFactory factory = Executors.defaultThreadFactory();
         
      // 使用AbortPolicy策略,該策略將會直接拋出RejectedExecutionException異常
       RejectedExecutionHandler abortHandler = new ThreadPoolExecutor.AbortPolicy();
      
      // 建立線程池
       ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue, factory, abortHandler);
    • 執行線程池

      這裏建立了一個HashMap,長度是30,裏面存了30個false。而後建立了一個線程類,
      接收這個map參數和一個int參數修改對應key的值爲true,最後調用execute()方法接收一個Thread對象來執行任務,也就是有30個任務修改map中30個key的value。

      // 一個map值都是false
      HashMap<Integer,Boolean> map = new HashMap<Integer,Boolean>();
      for (int l = 0; l < 30; l++) {
          map.put(l,false);
      }
         
      int count = 0;
      int i = 0;
      
      for (i = 0; i < 30; i++) {
          try {
              // 不能返回線程執行的結果
              executor.execute(new MyThread1(i,map));
              // 調用get方法就會阻塞當前線程直到完成
          } catch (Exception e) {
              System.out.println("被拒絕的任務的key: " + i);
              e.printStackTrace();
              count ++;
          }
      }
      
      // 在全部任務執行完後關閉線程池
      executor.shutdown();
      
      主線程睡眠一段時間,避免上面的for循環中線程池中的任務尚未執行完就開始打印結果
      //try {
      //    TimeUnit.SECONDS.sleep(10);
      //} catch (InterruptedException e) {
      //     e.printStackTrace();
      //}
      
      // 被拒絕執行的任務數量
      System.out.println("被拒絕的任務的數量: " + count);
         
      // 被拒絕執行的任務對應的map裏的值
      for(Entry<Integer,Boolean> entry:map.entrySet()) {
          if (entry.getValue() == false) {
              System.out.println("被拒絕的任務的key: " + entry.getKey());
          }
      }
      
      class MyThread1 extends Thread {
          private int i;
          private HashMap<Integer,Boolean> map;
      
          public MyThread1(int i,HashMap<Integer,Boolean> map) {
              this.map = map;
              this.i = i;
          }
      
          @Override
          public void run() {
      
              //try {
              //    TimeUnit.SECONDS.sleep(1);
              //} catch (InterruptedException e) {
              //    e.printStackTrace();
              //}
      
              // 修改map中對應的值
              map.put(i, true);
          }
      
      }
    • 執行結果

      上面的代碼中有三個地方打印了信息,第一個是catch塊中,由於使用的ThreadPoolExecutor. AbortPolicy()策略,而工做線程數只有1,最大線程數是2,30個循環有很大的可能性會有線程被拒絕,被拒絕以後就會拋出一個異常。這裏必定要catch住,否則整個線程池都會中止運行,第二個是打印被拒絕的任務的數量,第三個則是遍歷打印map看看value仍是false沒有被修改的key是否是和前面打印的對的上,驗證是否任務真的被拒絕了。注意這個結果是隨機的,能夠在線程類裏面睡眠一段時間,會發現頗有不少的任務被拒絕,由於執行一個任務的時間長了,線程池的工做線程更加沒法處理這麼多任務,可是要注意讓主線程打印結果前等待下,否則可能線程池的任務還沒執行就開始打印結果了,數據會不許。

      被拒絕的任務的key: 7
      java.util.concurrent.RejectedExecutionException: Task Thread[Thread-7,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@33909752[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]
      at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
      at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
      at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
      at com.lp.test.ThreadPool.main(ThreadPool.java:44)
      i error 15
      java.util.concurrent.RejectedExecutionException: Task Thread[Thread-15,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@33909752[Running, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 8]
      at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
      at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
      at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
      at com.lp.test.ThreadPool.main(ThreadPool.java:44)
      被拒絕的任務的數量: 1
      被拒絕的任務的key: 7
  3. 拒絕策略

    上面說過了線程池中設置了最大線程數,這個數比工做線程數要大,若是執行的任務數量超過了最大線程數就會根據拒絕策略來拒絕任務,可是有須要注意的地方,就是當執行的任務超出工做線程的數量時會把空閒的任務放入隊列中,只有當工做線程和隊列都滿了以後纔會去判斷數量是否超過了最大線程數。也就是說,若是設置工做線程數爲1,最大線程數爲1,隊列爲1000,這時來500個任務也不會執行拒絕操做,由於都放到隊列中了。

    • ThreadPoolExecutor.AbortPolicy()

      直接放棄任務而後拋出一個RejectedExecutionException異常

    • ThreadPoolExecutor.CallerRunsPolicy()

      會在調用線程池execute()方法的線程中執行被拒絕的任務,好比在main()方法中調用的execute()那麼就會在主線程中執行被拒絕的任務

    • ThreadPoolExecutor.DiscardOldestPolicy()

      會將隊列中最早等待的任務拋棄掉,也就是隊列的頭部元素,而後再嘗試提交任務,若是使用的是優先級別的隊列則會把優先級最高的元素丟掉

    • ThreadPoolExecutor.DiscardPolicy()

      直接放棄任務,可是不拋出任何錯誤,也不作任何操做

總結

  • 線程池中最重要的就是工做線程數、最大線程數、隊列大小和拒絕策略四個參數,決定了對系統性能的影響
  • 合理選用拒絕策略,若是選用ThreadPoolExecutor.AbortPolicy()策略,記得try住代碼,不然會影響後續代碼執行
  • 線程池用完後要調用shutdown()方法來關閉線程池,若是不調用這個方法會發現main方法執行完後也沒有關閉JVM虛擬機,調用這個方法後線程池會拒絕接受新的任務並等待現有的任務執行完以後再關閉線程池
相關文章
相關標籤/搜索