線程池詳述

1. 線程池的原理

ThreadPoolExecutor 是線程池的核心類,繼承了AbstractExecutorService類,AbstractExecutorService實現ExecutorService接口,ExecutorService接口繼承Executor
這裏寫圖片描述

1.1 構造方法中的參數

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
  • corePoolSize
    當線程池的線程數目達到corePoolSize的時候,就會把的任務放到緩存隊列中
  • maximumPoolSize
    表線程池最多能創建多少的線程數
  • keepAliveTime
    表示線程沒有任務執行時,保持多久會結束,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用
  • unit
    keepAliveTime的時間單位
  • workQueue
    阻塞隊列,存儲等待執行的任務 ,有ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;
  • threadFactory:
    線程工廠,主要用來創建線程
  • handler:
    表示當拒絕處理任務時的策略,有以下四種取值
    ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
    ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
    ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
    ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

1.2. ThreadPoolExecutor的方法

  • execute() 向線程池提交一個任務,交由線程池去執行
  • submit()也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果
  • shutdown()和shutdownNow()是用來關閉線程池

1.3.線程池狀態

  volatile int runState;
  static final int RUNNING    = 0;
  static final int SHUTDOWN   = 1;
  static final int STOP       = 2;
  static final int TERMINATED = 3;
  • runState表示當前線程池的狀態,volatile變量,用來保證線程之間的可見性;
  • RUNNING 線程池創建,初始時
  • SHUTDOWN 調用了shutdown(),線程不能接受新的任務,等待所有任務執行完畢
  • STOP 調用shutdownNow(),此時終止正在執行的任務。
  • TERMINATED 所有工作線程已經銷燬,任務緩存隊列已經清空。

1.4.任務提交,根據不同線程數來做相應處理

在ThreadPoolExecutor類中,最核心的任務提交方法是execute()方法

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
  }
  • 如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務,然後將線程放入池子中;
  • 如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;
  • 如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;
  • 如果線程池中的線程數量大於 corePoolSize時,線程空閒時間超過keepAliveTime,線程將被終;

1.5 總結:

(1)線程池初始化,默認初始化線程池中是沒有線程的,需要提交任務纔有。實踐中,可以先創建一定數量的線程。prestartCoreThread初始化一個核心線程,prestartAllCoreThread初始化所有核心線程
(2)任務提交的時候
如果線程數目小於corePoolSize,那麼就會創建線程去執行這個任務,將線程放入池子中;
如果maximumPoolSize>線程數目>=corePoolSize,那麼就會將任務添加到任務緩存隊列中(任務緩存隊列workQueue一般用LinkedBlockingQueue),等待空閒線程將其取出。如果添加失敗,說明緩存隊列滿了,需要重新創建一個線程。
如果線程數目達到如果maximumPoolSize,會採取拒絕策略處理。
同時線程數量>corePoolSize的時候,那麼線程空閒時間超過keepAliveTime,線程將會被終結。
(3)拒絕策略
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
(4)線程池關閉
shutdown():等所有任務緩存隊列的任務執行完再關閉,但不會接收新任務。 SHUTDOWN
shutdownNow():嘗試打斷正在執行的任務,並清空任務緩存隊列,返回正在執行的任務 STOP

2.四種線程池

2.1 Executors.newCachedThreadPool()
Executors.newCachedThreadPool(); //創建一個緩衝池,緩衝池容量大小爲Integer.MAX_VALUE
只有非核心線程,最大線程數很大(Int.Max(values)),它會爲每一個任務添加一個新的線程,這邊有一個超時機制,當空閒的線程超過60s內沒有用到的話,就會被回收。缺點就是沒有考慮到系統的實際內存大小。
這裏寫圖片描述

2.2 Executors.newSingleThreadExecutor()
只有一個核心線程。其他任務進來就進入等待隊列等待。
這裏寫圖片描述

2.3 Executors.newFixedThreadPool(int)
創建固定數量的核心線程數。
這裏寫圖片描述

2.4 Executors.newScheduledThreadPool
創建一個定長線程池,支持定時及週期性任務執行。
這裏寫圖片描述

3.線程池大小的設置

最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目

(1)高併發、任務執行時間短的業務,線程池線程數可以設置爲CPU核數+1,減少線程上下文的切換
(2)併發不高、任務執行時間長的業務要區分開看
假如是業務時間長集中在IO操作上,也就是IO密集型的任務,因爲IO操作並不佔用CPU,所以不要讓所有的CPU閒下來,可以適當加大線程池中的線程數目,讓CPU處理更多的業務
假如是業務時間長集中在計算操作上,也就是計算密集型任務,線程池中的線程數設置得少一些,減少線程上下文的切換
(3)併發高、業務執行時間長,解決這種類型任務的關鍵不在於線程池而在於整體架構的設計,看看這些業務裏面某些數據是否能做緩存是第一步,增加服務器是第二步,至於線程池的設置,設置參考(2)。最後,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。

4.Future&FutureTask

submit 方法 可以返回任務執行後的結果
將實現Callable接口的task進行submint,然後會返回一個用Future接口接收返回,get()方法就是你需要返回的信息,get()的時候,會從completionQueue隊列中去取,然後返回Future接口對象。好處是不用輪詢等待,直接get的話要等任務執行完,否則要調用awaitDone阻塞,輪詢查看futureTask的狀態。
這裏寫圖片描述

Callable是一個泛型接口,call()函數返回的類型就是傳遞進來的泛型實參類型

public interface Callable<V> {
        V call() throws Exception;
}

FutureTask
實現了RunnableFuture接口,RunnableFuture接口繼承了Runnable,Future。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
public class FutureTask<V> implements RunnableFuture<V>
public Future<V> submit(Callable<V> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<V> f = newTaskFor(task);
        executor.execute(new QueueingFuture(f));
        return f
}
private class QueueingFuture extends FutureTask<Void> {
    QueueingFuture(RunnableFuture<V> task) {
      super(task, null);
      this.task = task;
    }
    protected void done() { completionQueue.add(task); }
    private final Future<V> task;
}

參考:https://www.cnblogs.com/exe19/p/5359885.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章