1. 程式人生 > >java多執行緒與執行緒池

java多執行緒與執行緒池

1. 場景描述

以前多執行緒也常用,這次因需再頁面上用到多執行緒,如下圖,總結下,有需要的朋友可以參考下。

2. 解決方案

2.1 執行緒池概念

執行緒池官方定義不說了,通俗說下:池子的概念,事先(預定義)建立後,後續的執行緒可以直接從池子中拿,好處:

(1)來建立執行緒比較消耗資源,不用重複建立;

(2)池子事先定義好,避免無節制建立執行緒,導致系統出現不可預測風險。

2.2 建立方式

採用jdk自帶的執行緒池建立方式,jdk1.5開始提供,在java.util.concurrent 的包下面。

表面上有兩種建立方式,其實一種。

(1)一種是採用new ThreadPoolExecutor進行建立;

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

(2)一種是採用Executors.newFixedThreadPool(3),不過還是呼叫的new ThreadPoolExecutor進行的執行緒池建立,賦值了幾個預設引數而已。

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

2.3 引數含義

一共有 7 個引數:

(1)corePoolSize

核心執行緒數,當有任務進來的時候,如果當前執行緒數還未達到 corePoolSize 個數,則建立核心執行緒,核心執行緒有幾個特點:

1、當執行緒數未達到核心執行緒最大值的時候,新任務進來,即使有空閒執行緒,也不會複用,仍然新建核心執行緒;2、核心執行緒一般不會被銷燬,即使是空閒的狀態,但是如果通過方法 allowCoreThreadTimeOut(boolean value) 設定為 true 時,超時也同樣會被銷燬;3、生產環境首次初始化的時候,可以呼叫 prestartCoreThread() 方法來預先建立所有核心執行緒,避免第一次呼叫緩慢;

(2)maximumPoolSize

除了有核心執行緒外,有些策略是當核心執行緒完全無空閒的時候,還會建立一些臨時的執行緒來處理任務,maximumPoolSize 就是核心執行緒 + 臨時執行緒的最大上限。臨時執行緒有一個超時機制,超過了設定的空閒時間沒有事兒幹,就會被銷燬。

(3)keepAliveTime

這個就是上面兩個引數裡所提到的超時時間,也就是執行緒的最大空閒時間,預設用於非核心執行緒,通過 allowCoreThreadTimeOut(boolean value) 方法設定後,也會用於核心執行緒。

(4)unit

這個引數配合上面的 keepAliveTime ,指定超時的時間單位,秒、分、時等。

(5)workQueue

等待執行的任務佇列,如果核心執行緒沒有空閒的了,新來的任務就會被放到這個等待佇列中。

(6)threadFactory

它是一個介面,用於實現生成執行緒的方式、定義執行緒名格式、是否後臺執行等等,可以用 Executors.defaultThreadFactory() 預設的實現即可,也可以用 Guava 等三方庫提供的方法實現,如果有特殊要求的話可以自己定義。它最重要的地方應該就是定義執行緒名稱的格式,便於排查問題了吧。

(7)handler

當沒有空閒的執行緒處理任務,並且等待佇列已滿(當然這隻對有界佇列有效),再有新任務進來的話,就要做一些取捨了,而這個引數就是指定取捨策略的,有下面四種策略可以選擇:

ThreadPoolExecutor.AbortPolicy:直接丟擲異常,這是預設策略;
ThreadPoolExecutor.DiscardPolicy:直接丟棄任務,但是不丟擲異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後將新來的任務加入等待佇列
ThreadPoolExecutor.CallerRunsPolicy:由執行緒池所在的執行緒處理該任務,比如在 main 函式中建立執行緒池,如果執行此策略,將有 main 執行緒來執行該任務

2.4 測試驗證

2.4.1 測試執行緒
package com.yutong.laowang.test;

public class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println("軟體老王:" +Thread.currentThread().getName());
  }
}
2.4.2 Executors建立
  private static void test4() {
        Executor mExecutor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            Thread thread = new ThreadTest();
            mExecutor.execute(thread);
        }
    }

結果:

軟體老王:pool-2-thread-2
軟體老王:pool-2-thread-3
軟體老王:pool-2-thread-1
軟體老王:pool-2-thread-3
軟體老王:pool-2-thread-2
軟體老王:pool-2-thread-1
軟體老王:pool-2-thread-3
軟體老王:pool-2-thread-2
軟體老王:pool-2-thread-3
軟體老王:pool-2-thread-1
2.4.3 ThreadPoolExecutor建立
 private static void test3() {
        int poolSize = 5;
        int queueSize = 100;
        ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(queueSize), new ThreadPoolExecutor.AbortPolicy());
                
        for (int i=0;i<10;i++) {
            executorService.submit(new ThreadTest());
        }
    }

結果:

軟體老王:pool-2-thread-1
軟體老王:pool-2-thread-3
軟體老王:pool-2-thread-4
軟體老王:pool-2-thread-2
軟體老王:pool-2-thread-5
軟體老王:pool-2-thread-5
軟體老王:pool-2-thread-1
軟體老王:pool-2-thread-2
軟體老王:pool-2-thread-2
軟體老王:pool-2-thread-1
2.4.4 執行緒停用
thread.interrupt();
---有時候不一定能執行成功,一般會結合判斷使用,軟體老王,例如:
在ThreadTest類中進行判斷,預設為false,當滿足條件下為true,停用執行緒。
 public volatile boolean exit = false;

I’m 「軟體老王」,如果覺得還可以的話,關注下唄,後續更新秒知!歡迎討論區、同名公眾號留言交流