Java多執行緒-執行緒池ThreadPoolExecutor構造方法和規則

weixin_34320159發表於2017-05-03

為什麼用執行緒池

部落格地址 http://blog.csdn.net/qq_25806863

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

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

使用執行緒池主要為了解決一下幾個問題:

  • 通過重用執行緒池中的執行緒,來減少每個執行緒建立和銷燬的效能開銷。
  • 對執行緒進行一些維護和管理,比如定時開始,週期執行,併發數控制等等。

Executor

Executor是一個介面,跟執行緒池有關的基本都要跟他打交道。下面是常用的ThreadPoolExecutor的關係。

2062943-651ba373300537c3.png

Executor介面很簡單,只有一個execute方法。

ExecutorService是Executor的子介面,增加了一些常用的對執行緒的控制方法,之後使用執行緒池主要也是使用這些方法。

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

ThreadPoolExecutor

構造方法

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

2062943-5cd1b3b85074bbd5.png
  • 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各奔不儲存任務,收到一個任務就去建立新執行緒。所以第五個就會拋異常了。

相關文章