JAVA線程池

應用中應避免顯式直接創建線程示例去執行

線程池避免了大量創建與銷燬線程所需要的成本,也可以避免峯值壓力帶來的瞬間大量線程創建帶來的資源耗盡,程序崩潰的風險

JUC包對線程池的支持

在JDK的Java.util.concurrent包下Executors類利用工廠模式向我們提供了4種線程池實現方式

Java開發手冊中的規約

阿里發佈的 Java開發手冊中強制線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險

ThreadPoolExecutor構造方法中需要的參數:

public ThreadPoolExecutor(//Executors類創建線程池也是底層使用這個類創建的
    int corePoolSize,//核心線程數
    int maximumPoolSize,//線程池最大線程數
    long keepAliveTime,//保持時間,當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。
    TimeUnit unit,//是一個枚舉,表示 keepAliveTime 的單位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7個可選值)。
    BlockingQueue<Runnable> workQueue,//存放任務的隊列(存放需要被線程池執行的線程隊列)。
    RejectedExecutionHandler handler;//拒絕策略(添加任務失敗後如何處理該任務)
);

簡單的線程池創建示例:

public class ThreadPoolTest implements Runnable {
    public void run() {
      synchronized(this) {
        try{
          System.out.println(Thread.currentThread().getName());
          Thread.sleep(3000);
        }catch (InterruptedException e){
          e.printStackTrace();
        }
      }
    }
 
   public static void main(String[] args) {
      BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
      ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 6, 1, TimeUnit.DAYS, queue);
      for (int i = 0; i < 10; i++) {
        executor.execute(new Thread(new ThreadPoolTest(),"TestThread".concat(""+i)));
        int threadSize = queue.size();
        System.out.println("線程隊列大小爲-->"+threadSize);
      }
      executor.shutdown();
    }
}

阻塞隊列

JDK 7 提供了7個阻塞隊列,如下

1、ArrayBlockingQueue 數組結構組成的有界阻塞隊列。

此隊列按照先進先出(FIFO)的原則對元素進行排序,但是默認情況下不保證線程公平的訪問隊列,即如果隊列滿了,那麼被阻塞在外面的線程對隊列訪問的順序是不能保證線程公平(即先阻塞,先插入)的。

2、LinkedBlockingQueue一個由鏈表結構組成的有界阻塞隊列

此隊列按照先出先進的原則對元素進行排序

3、PriorityBlockingQueue支持優先級的無界阻塞隊列

4、DelayQueue支持延時獲取元素的無界阻塞隊列,即可以指定多久才能從隊列中獲取當前元素

5、SynchronousQueue不存儲元素的阻塞隊列,每一個put必須等待一個take操作,否則不能繼續添加元素。並且他支持公平訪問隊列。

6、LinkedTransferQueue由鏈表結構組成的無界阻塞TransferQueue隊列。相對於其他阻塞隊列,多了tryTransfer和transfer方法

transfer方法:

如果當前有消費者正在等待接收元素(take或者待時間限制的poll方法),transfer可以把生產者傳入的元素立刻傳給消費者。如果沒有消費者等待接收元素,則將元素放在隊列的tail節點,並等到該元素被消費者消費了才返回。

tryTransfer方法:

用來試探生產者傳入的元素能否直接傳給消費者。,如果沒有消費者在等待,則返回false。和上述方法的區別是該方法無論消費者是否接收,方法立即返回。而transfer方法是必須等到消費者消費了才返回。

7、LinkedBlockingDeque鏈表結構的雙向阻塞隊列,優勢在於多線程入隊時,減少一半的競爭。

拒絕策略

拒絕策略也有四種:

ThreadPoolExecutor.AbortPolicy: 丟棄任務並拋出異常 (默認策略)。

ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋出異常。

ThreadPoolExecutor.DiscardOldestPolicy:將最早進入隊列的任務刪,之後再嘗試加入隊列。

ThreadPoolExecutor.CallerRunsPolicy:如果添加到線程池失敗,那麼主線程會自己去執行該任務。

線程運行策略

1.線程池剛創建時,裏面沒有一個線程。任務隊列是作爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會馬上執行它們。

2.當調用 execute() 方法添加一個任務時,線程池會做如下判斷:

a. 如果正在運行的線程數量小於 corePoolSize,那麼馬上創建線程運行這個任務;
b. 如果正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列。
c. 如果這時候隊列滿了,而且正在運行的線程數量小於 maximumPoolSize,那麼還是要創建線程運行這個任務;
d. 如果隊列滿了,而且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會拋出異常,告訴調用者“我不能再接受任務了”。

3.當一個線程完成任務時,它會從隊列中取下一個任務來執行。

4.當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行 的線程數大於 corePoolSize,那麼這個線程就被停掉。所以線程池的所有任務完成後,它最終會收縮到 corePoolSize 的大小。

五種線程池的區別及使用場景

1、newCachedThreadPool適合任務量大但耗時少的任務

new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
60L, TimeUnit.SECONDS, 
new SynchronousQueue<Runnable>())

任務隊列採用的是SynchronousQueue,這個隊列是無法插入任務的,一有任務立即執行。

作用:創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們,並在需要時使用提供的 ThreadFactory 創建新線程。

特徵:
(1)線程池中數量沒有固定,可達到最大值(Interger. MAX_VALUE = 2n31-1)
(2)線程池中的線程可進行緩存重複利用和回收(回收默認時間爲1分鐘)
(3)當線程池中,沒有可用線程,會重新創建一個線程
(4)無核心線程,所有線程都是非核心線程

提交任務時,如果線程都處於活動狀態則創建新線程,如果線程超過(keepAliveTime)60秒空閒會被回收,如果線程池空閒會回收所有線程

創建方式: Executors.newCachedThreadPool();

2、newFixedThreadPool —— 適用於任務量比較固定但耗時長的任務

new ThreadPoolExecutor(nThreads, nThreads, 
0L, TimeUnit.MILLISECONDS, 
new LinkedBlockingQueue<Runnable>())

任務隊列採用了無界的阻塞隊列LinkedBlockingQueue,執行execute方法的時候,運行的線程沒有達到corePoolSize就創建核心線程執行任務,否則就阻塞在任務隊列中,有空閒線程的時候去取任務執行。

作用:創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。在任意點,在大多數 nThreads 線程會處於處理任務的活動狀態。如果在所有線程處於活動狀態時提交附加任務,則在有可用線程之前,附加任務將在隊列中等待。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。在某個線程被顯式地關閉之前,池中的線程將一直存在。

特徵:
(1)線程池中的線程處於一定的量,可以很好的控制線程的併發量
(2)線程可以重複被使用,在顯示關閉之前,都將一直存在
(3)超出一定量的線程被提交時候需在隊列中等待
(4)全是核心線程,無非核心線程

無超時機制,無任務大小限制,數量固定,空閒不回收。

創建方式:
(1)Executors.newFixedThreadPool(int nThreads);//nThreads爲線程的數量
(2)Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory);//nThreads爲線程的數量,threadFactory創建線程的工廠方式

3、newSingleThreadExecutor ——適用於多個任務順序執行(當任務依賴上一個任務的結果時,就可以考慮 SingleThreadExecutor)的場景

new ThreadPoolExecutor(1, 1, 
0L, TimeUnit.MILLISECONDS, 
new LinkedBlockingQueue<Runnable>())

任務隊列是LinkedBlockingQueue,這是個無界的阻塞隊列,因爲線程池裏只有一個線程,就確保所有的任務都在同一個線程中順序執行,這樣就不需要處理線程同步的問題。

作用:創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。(注意,如果因爲在關閉前的執行期間出現失敗而終止了此單個線程,那麼如果需要,一個新線程將代替它執行後續的任務)。可保證順序地執行各個任務,並且在任意給定的時間不會有多個線程是活動的。與其他等效的newFixedThreadPool(1) 不同,可保證無需重新配置此方法所返回的執行程序即可使用其他的線程。

特徵:
(1)線程池中最多執行1個線程,之後提交的線程活動將會排在隊列中以此執行
(2)只擁有一個核心線程

可以單獨用,也可以與週期線程池結合

創建方式:
(1)Executors.newSingleThreadExecutor() ;
(2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);// threadFactory創建線程的工廠方式

4、newScheduleThreadPool —— 這類線程池適用於執行定時任務和具體固定週期的重複任務

new ScheduledThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 
0, NANOSECONDS, 
new DelayedWorkQueue())

任務隊列採用的DelayedWorkQueue是個無界的隊列,延時執行隊列任務。

作用: 創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。

特徵:
(1)線程池中具有指定數量的線程,即便是空線程也將保留
(2)可定時或者延遲執行線程活動
(3)核心線程數固定,不回收;非核心線程無限制,空閒即回收

創建方式:
(1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize線程的個數
(2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize線程的個數,threadFactory創建線程的工廠

5、newSingleThreadScheduledExecutor
作用: 創建一個單線程執行程序,它可安排在給定延遲後運行命令或者定期地執行。

特徵:
(1)線程池中最多執行1個線程,之後提交的線程活動將會排在隊列中以此執行
(2)可定時或者延遲執行線程活動

創建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory) ;//threadFactory創建線程的工廠

開發中應根據實際場景使用ThreadPoolExecutor創建合適的線程池,例如:IO密集型任務,線程多數時間在等待IO完成,所有應創建更多的線程來充分利用CPU資源(2N)
CPU密集型任務,線程完成任務時間較短,創建少量線程可以避免線程切換帶來的開銷(N+1)

**N爲CPU核心數

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