1. 程式人生 > >Java多線程-線程池ThreadPoolExecutor構造方法和規則

Java多線程-線程池ThreadPoolExecutor構造方法和規則

解決 $1 核心 keepaliv 狀態 被拒絕 live link 限時

為什麽用線程池

博客地址 http://blog.csdn.net/qq_25806863

原文地址 http://blog.csdn.net/qq_25806863/article/details/71126867

有時候,系統需要處理非常多的執行時間很短的請求,如果每一個請求都開啟一個新線程的話,系統就要不斷的進行線程的創建和銷毀,有時花在創建和銷毀線程上的時間會比線程真正執行的時間還長。而且當線程數量太多時,系統不一定能受得了。

使用線程池主要為了解決一下幾個問題:

  • 通過重用線程池中的線程,來減少每個線程創建和銷毀的性能開銷。
  • 對線程進行一些維護和管理,比如定時開始,周期執行,並發數控制等等。

Executor

Executor是一個接口,跟線程池有關的基本都要跟他打交道。下面是常用的ThreadPoolExecutor的關系。

技術分享圖片

Executor接口很簡單,只有一個execute方法。

ExecutorService是Executor的子接口,增加了一些常用的對線程的控制方法,之後使用線程池主要也是使用這些方法。

AbstractExecutorService是一個抽象類。ThreadPoolExecutor就是實現了這個類。

ThreadPoolExecutor

構造方法

ThreadPoolExecutor是線程池的真正實現,他通過構造方法的一系列參數,來構成不同配置的線程池。常用的構造方法有下面四個:

技術分享圖片

  • ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue) 
    • 1
    • 2
    • 3
    • 4
    • 5
  • ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,
                            ThreadFactory threadFactory)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,
                            RejectedExecutionHandler handler)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,
                            ThreadFactory threadFactory,
                            RejectedExecutionHandler handler)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

構造方法參數說明

  • corePoolSize

    核心線程數,默認情況下核心線程會一直存活,即使處於閑置狀態也不會受存keepAliveTime限制。除非將allowCoreThreadTimeOut設置為true

  • maximumPoolSize

    線程池所能容納的最大線程數。超過這個數的線程將被阻塞。當任務隊列為沒有設置大小的LinkedBlockingDeque時,這個值無效。

  • keepAliveTime

    非核心線程的閑置超時時間,超過這個時間就會被回收。

  • unit

    指定keepAliveTime的單位,如TimeUnit.SECONDS。當將allowCoreThreadTimeOut設置為true時對corePoolSize生效。

  • workQueue

    線程池中的任務隊列.

    常用的有三種隊列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue

  • threadFactory

    線程工廠,提供創建新線程的功能。ThreadFactory是一個接口,只有一個方法

    public interface ThreadFactory {
      Thread newThread(Runnable r);
    }
    • 1
    • 2
    • 3

    通過線程工廠可以對線程的一些屬性進行定制。

    默認的工廠:

    static class DefaultThreadFactory implements ThreadFactory {
      private static final AtomicInteger poolNumber = new AtomicInteger(1);
      private final ThreadGroup group;
      private final AtomicInteger threadNumber = new AtomicInteger(1);
      private final String namePrefix;
    
      DefaultThreadFactory() {
          SecurityManager var1 = System.getSecurityManager();
          this.group = var1 != null?var1.getThreadGroup():Thread.currentThread().getThreadGroup();
          this.namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
      }
    
      public Thread newThread(Runnable var1) {
          Thread var2 = new Thread(this.group, var1, this.namePrefix + this.threadNumber.getAndIncrement(), 0L);
          if(var2.isDaemon()) {
              var2.setDaemon(false);
          }
    
          if(var2.getPriority() != 5) {
              var2.setPriority(5);
          }
    
          return var2;
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • RejectedExecutionHandler

    RejectedExecutionHandler也是一個接口,只有一個方法

    public interface RejectedExecutionHandler {
      void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
    }
    • 1
    • 2
    • 3

    當線程池中的資源已經全部使用,添加新線程被拒絕時,會調用RejectedExecutionHandler的rejectedExecution方法。

線程池規則

線程池的線程執行規則跟任務隊列有很大的關系。

  • 下面都假設任務隊列沒有大小限制:

    1. 如果線程數量<=核心線程數量,那麽直接啟動一個核心線程來執行任務,不會放入隊列中。
    2. 如果線程數量>核心線程數,但<=最大線程數,並且任務隊列是LinkedBlockingDeque的時候,超過核心線程數量的任務會放在任務隊列中排隊。
    3. 如果線程數量>核心線程數,但<=最大線程數,並且任務隊列是SynchronousQueue的時候,線程池會創建新線程執行任務,這些任務也不會被放在任務隊列中。這些線程屬於非核心線程,在任務完成後,閑置時間達到了超時時間就會被清除。
    4. 如果線程數量>核心線程數,並且>最大線程數,當任務隊列是LinkedBlockingDeque,會將超過核心線程的任務放在任務隊列中排隊。也就是當任務隊列是LinkedBlockingDeque並且沒有大小限制時,線程池的最大線程數設置是無效的,他的線程數最多不會超過核心線程數。
    5. 如果線程數量>核心線程數,並且>最大線程數,當任務隊列是SynchronousQueue的時候,會因為線程池拒絕添加任務而拋出異常。
  • 任務隊列大小有限時

    1. 當LinkedBlockingDeque塞滿時,新增的任務會直接創建新線程來執行,當創建的線程數量超過最大線程數量時會拋異常。
    2. SynchronousQueue沒有數量限制。因為他根本不保持這些任務,而是直接交給線程池去執行。當任務數量超過最大線程數時會直接拋異常。

規則驗證

前提

所有的任務都是下面這樣的,睡眠兩秒後打印一行日誌:

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

所有驗證過程都是下面這樣,先執行三個,再執行三個,8秒後,各看一次信息

executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("---先開三個---");
System.out.println("核心線程數" + executor.getCorePoolSize());
System.out.println("線程池數" + executor.getPoolSize());
System.out.println("隊列任務數" + executor.getQueue().size());
executor.execute(myRunnable);
executor.execute(myRunnable);
executor.execute(myRunnable);
System.out.println("---再開三個---");
System.out.println("核心線程數" + executor.getCorePoolSize());
System.out.println("線程池數" + executor.getPoolSize());
System.out.println("隊列任務數" + executor.getQueue().size());
Thread.sleep(8000);
System.out.println("----8秒之後----");
System.out.println("核心線程數" + executor.getCorePoolSize());
System.out.println("線程池數" + executor.getPoolSize());
System.out.println("隊列任務數" + executor.getQueue().size());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

驗證1

  1. 核心線程數為6,最大線程數為10。超時時間為5秒

    ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 10, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    • 1
    ---先開三個---
    核心線程數6
    線程池線程數3
    隊列任務數0
    ---再開三個---
    核心線程數6
    線程池線程數6
    隊列任務數0
    pool-1-thread-1 run
    pool-1-thread-6 run
    pool-1-thread-5 run
    pool-1-thread-3 run
    pool-1-thread-4 run
    pool-1-thread-2 run
    ----8秒之後----
    核心線程數6
    線程池線程數6
    隊列任務數0
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

可以看到每個任務都是是直接啟動一個核心線程來執行任務,一共創建了6個線程,不會放入隊列中。8秒後線程池還是6個線程,核心線程默認情況下不會被回收,不收超時時間限制。

驗證2

  1. 核心線程數為3,最大線程數為6。超時時間為5秒,隊列是LinkedBlockingDeque

    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
    • 1
    ---先開三個---
    核心線程數3
    線程池線程數3
    隊列任務數0
    ---再開三個---
    核心線程數3
    線程池線程數3
    隊列任務數3
    pool-1-thread-3 run
    pool-1-thread-1 run
    pool-1-thread-2 run
    pool-1-thread-3 run
    pool-1-thread-1 run
    pool-1-thread-2 run
    ----8秒之後----
    核心線程數3
    線程池線程數3
    隊列任務數0
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    當任務數超過核心線程數時,會將超出的任務放在隊列中,只會創建3個線程重復利用。

驗證3

  1. 核心線程數為3,最大線程數為6。超時時間為5秒,隊列是SynchronousQueue
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
  • 1
---先開三個---
核心線程數3
線程池線程數3
隊列任務數0
---再開三個---
核心線程數3
線程池線程數6
隊列任務數0
pool-1-thread-2 run
pool-1-thread-3 run
pool-1-thread-6 run
pool-1-thread-4 run
pool-1-thread-5 run
pool-1-thread-1 run
----8秒之後----
核心線程數3
線程池線程數3
隊列任務數0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

當隊列是SynchronousQueue時,超出核心線程的任務會創建新的線程來執行,看到一共有6個線程。但是這些線程是費核心線程,收超時時間限制,在任務完成後限制超過5秒就會被回收。所以最後看到線程池還是只有三個線程。

驗證4

  1. 核心線程數是3,最大線程數是4,隊列是LinkedBlockingDeque

    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
    • 1
---先開三個---
核心線程數3
線程池線程數3
隊列任務數0
---再開三個---
核心線程數3
線程池線程數3
隊列任務數3
pool-1-thread-3 run
pool-1-thread-1 run
pool-1-thread-2 run
pool-1-thread-3 run
pool-1-thread-1 run
pool-1-thread-2 run
----8秒之後----
核心線程數3
線程池線程數3
隊列任務數0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

LinkedBlockingDeque根本不受最大線程數影響。

但是當LinkedBlockingDeque有大小限制時就會受最大線程數影響了

4.1 比如下面,將隊列大小設置為2.

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(2));
  • 1
---先開三個---
核心線程數3
線程池線程數3
隊列任務數0
---再開三個---
核心線程數3
線程池線程數4
隊列任務數2
pool-1-thread-2 run
pool-1-thread-1 run
pool-1-thread-4 run
pool-1-thread-3 run
pool-1-thread-1 run
pool-1-thread-2 run
----8秒之後----
核心線程數3
線程池線程數3
隊列任務數0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

首先為三個任務開啟了三個核心線程1,2,3,然後第四個任務和第五個任務加入到隊列中,第六個任務因為隊列滿了,就直接創建一個新線程4,這是一共有四個線程,沒有超過最大線程數。8秒後,非核心線程收超時時間影響回收了,因此線程池只剩3個線程了。

4.2 將隊列大小設置為1

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(1));
  • 1
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.sunlinlin.threaddemo.Main$1@677327b6 rejected from java.util.concurrent.ThreadPoolExecutor@14ae5a5[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at com.sunlinlin.threaddemo.Main.main(Main.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
---先開三個---
核心線程數3
線程池線程數3
隊列任務數0
pool-1-thread-1 run
pool-1-thread-2 run
pool-1-thread-3 run
pool-1-thread-4 run
pool-1-thread-1 run
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

直接出錯在第6個execute方法上。因為核心線程是3個,當加入第四個任務的時候,就把第四個放在隊列中。加入第五個任務時,因為隊列滿了,就創建新線程執行,創建了線程4。當加入第六個線程時,也會嘗試創建線程,但是因為已經達到了線程池最大線程數,所以直接拋異常了。

驗證5

  1. 核心線程數是3 ,最大線程數是4,隊列是SynchronousQueue

    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    • 1
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.sunlinlin.threaddemo.Main$1@14ae5a5 rejected from java.util.concurrent.ThreadPoolExecutor@7f31245a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at com.sunlinlin.threaddemo.Main.main(Main.java:34)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
---先開三個---
核心線程數3
線程池線程數3
隊列任務數0
pool-1-thread-2 run
pool-1-thread-3 run
pool-1-thread-4 run
pool-1-thread-1 run
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

這次在添加第五個任務時就報錯了,因為SynchronousQueue各奔不保存任務,收到一個任務就去創建新線程。所以第五個就會拋異常了。

Java多線程-線程池ThreadPoolExecutor構造方法和規則