Java多線程-線程池ThreadPoolExecutor構造方法和規則
為什麽用線程池
博客地址 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方法。
線程池規則
線程池的線程執行規則跟任務隊列有很大的關系。
-
下面都假設任務隊列沒有大小限制:
- 如果線程數量<=核心線程數量,那麽直接啟動一個核心線程來執行任務,不會放入隊列中。
- 如果線程數量>核心線程數,但<=最大線程數,並且任務隊列是LinkedBlockingDeque的時候,超過核心線程數量的任務會放在任務隊列中排隊。
- 如果線程數量>核心線程數,但<=最大線程數,並且任務隊列是SynchronousQueue的時候,線程池會創建新線程執行任務,這些任務也不會被放在任務隊列中。這些線程屬於非核心線程,在任務完成後,閑置時間達到了超時時間就會被清除。
- 如果線程數量>核心線程數,並且>最大線程數,當任務隊列是LinkedBlockingDeque,會將超過核心線程的任務放在任務隊列中排隊。也就是當任務隊列是LinkedBlockingDeque並且沒有大小限制時,線程池的最大線程數設置是無效的,他的線程數最多不會超過核心線程數。
- 如果線程數量>核心線程數,並且>最大線程數,當任務隊列是SynchronousQueue的時候,會因為線程池拒絕添加任務而拋出異常。
-
任務隊列大小有限時
- 當LinkedBlockingDeque塞滿時,新增的任務會直接創建新線程來執行,當創建的線程數量超過最大線程數量時會拋異常。
- 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
-
核心線程數為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
-
核心線程數為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
- 核心線程數為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
-
核心線程數是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
-
核心線程數是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構造方法和規則