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) 
    
  • ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)
    
  • ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
    
  • ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    

構造方法參數說明

  • 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);
    }
    

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

    默認的工廠:

    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;
        }
    }
    
  • RejectedExecutionHandler

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

    public interface RejectedExecutionHandler {
        void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
    }
    

    當線程池中的資源已經全部使用,添加新線程被拒絕時,會調用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();
        }

    }
};

所有驗證過程都是下面這樣,先執行三個,再執行三個,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

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

    ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 10, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    
---先開三個---
核心線程數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

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

驗證2

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

    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
    
    ---先開三個---
    核心線程數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
    

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

驗證3

  1. 核心線程數為3,最大線程數為6。超時時間為5秒,隊列是SynchronousQueue
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
---先開三個---
核心線程數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

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

驗證4

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

    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
    
---先開三個---
核心線程數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

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

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

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

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(2));
---先開三個---
核心線程數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,這是一共有四個線程,沒有超過最大線程數。8秒后,非核心線程收超時時間影響回收了,因此線程池只剩3個線程了。

4.2 將隊列大小設置為1

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(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

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

驗證5

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

    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    
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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本篇文章講述Java中的線程池問題,同樣適用于Android中的線程池使用。本篇文章參考:Java線程池分析,Ja...
    Android進階與總結閱讀 837評論 0 5
  • 先看幾個概念:線程:進程中負責程序執行的執行單元。一個進程中至少有一個線程。多線程:解決多任務同時執行的需求,合理...
    yeying12321閱讀 570評論 0 0
  • 本篇文章講述Java中的線程池問題,同樣適用于Android中的線程池使用。本篇文章參考:Java線程池分析,Ja...
    Ruheng閱讀 7,189評論 1 64
  • 前段時間遇到這樣一個問題,有人問微信朋友圈的上傳圖片的功能怎么做才能讓用戶的等待時間較短,比如說一下上傳9張圖片,...
    加油碼農閱讀 1,217評論 0 2
  • 我,買了一張票,買到了一張南寧的票。我不顧多想,打包好,即刻出發…… 突然間,我才知道我并不熟悉這個城市,卻已...
    手記人閱讀 161評論 0 0