【Java線程池(一)】

Java線程池的簡單瞭解

1 參考資料

  1. Java併發編程藝術

2 線程池執行原理

一圖勝千言,越來越覺得有道理,所有,我要上圖了~~

看圖之前要先知道Java線程池的組成(可以看圖二):

  • 核心線程池-就是我們自己初始化線程池時設定的線程數目。(相當於正式員工)
  • 阻塞隊列-核心線程池滿後就去排隊
  • 最大線程池-隊列滿後纔去創建,可以創建的數目爲,最大線程池線程數-核心線程池線程數(相當於臨時工,任務太多了)
  • 拒絕執行策略(任務更多了,又沒錢再招臨時工了,所以新的任務是放棄還是幹啥?)
    在這裏插入圖片描述

在這裏插入圖片描述
這兩張圖都是對線程池的運行過程的描述,後一張是前面一張的更具體描述。

過程簡要描述如下:

提交一個需要被執行的任務時,

  1. 如果已經存在線程數目小於核心線程數,創建新線程執行任務,否則放入阻塞隊列
  2. 如果阻塞隊列沒滿,任務就在裏面等待
  3. 如果阻塞隊列滿了,就查看,當前線程數量是否小於最大線程數,如果小於,那就創建新線程
  4. 否則,那就交個飽和策略處理。

3 線程池初始化參數瞭解

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

看看JDK中的源碼,發現一句 It may be more convenient to use one of the {@link Executors} factory methods instead of this general purpose constructor.(使用 Executors 框架中封裝好的工廠方法會更方便 ,而不是使用這個通用的構造函數 )。所以,Executors 是更常用的線程池使用方法。

但是,阿里的Java開發手冊卻禁止使用Executors框架,推薦直接使用ThreadPoolExecutor
在這裏插入圖片描述

現在來看看參數(註釋已經寫得很清楚了,其實)

  1. corePoolSize
  2. maximumPoolSize
  3. keepAliveTime 超出核心線程數的那些線程當處於空閒時的保存時間(臨時工如果在規定時間內沒接到任務就辭退)
  4. unit ,keepAliveTime的時間單位
  5. workQueue 等待隊列
    • ArrayBlockingQueue 基於數組結構的有界阻塞隊列
    • LinkedBlockingQueue 基於鏈表結構的有界阻塞隊列
    • SynchronousQueue 一個不存儲元素的阻塞隊列
    • PriorityBlockingQueue 一個具有優先級的無界阻塞隊列
  6. Executors.defaultThreadFactory() 工廠方法,通過工廠方法給每個線程設置更有意義的名字
  7. defaultHandler 拒絕策略
    • AbortionPolicy :直接拋出異常
    • CallerRunsPolicy:只用調用者所在的線程運行任務
    • DiscardOlderestPolicy:丟棄隊列裏最近的一個任務,然後執行當前任務
    • DiscardPolicy:直接丟棄,不搭理

4 線程池的使用

4.1 創建

上面已經解釋了構造函數~

4.2 使用

主要有兩種方式:

  • execute() 用於提交不需要返回值的任務
  /**
       * Executes the given task sometime in the future.  The task
       * may execute in a new thread or in an existing pooled thread.
       *
       * If the task cannot be submitted for execution, either because this
       * executor has been shutdown or because its capacity has been reached,
       * the task is handled by the current {@code RejectedExecutionHandler}.
       *
       * @param command the task to execute
       * @throws RejectedExecutionException at discretion of
       *         {@code RejectedExecutionHandler}, if the task
       *         cannot be accepted for execution
       * @throws NullPointerException if {@code command} is null
       */
      public void execute(Runnable command) {
          if (command == null)
              throw new NullPointerException();
          /*
           * Proceed in 3 steps:
           *
           * 1. If fewer than corePoolSize threads are running, try to
           * start a new thread with the given command as its first
           * task.  The call to addWorker atomically checks runState and
           * workerCount, and so prevents false alarms that would add
           * threads when it shouldn't, by returning false.
           *
           * 2. If a task can be successfully queued, then we still need
           * to double-check whether we should have added a thread
           * (because existing ones died since last checking) or that
           * the pool shut down since entry into this method. So we
           * recheck state and if necessary roll back the enqueuing if
           * stopped, or start a new thread if there are none.
           *
           * 3. If we cannot queue task, then we try to add a new
           * thread.  If it fails, we know we are shut down or saturated
           * and so reject the task.
           */
          int c = ctl.get();
          if (workerCountOf(c) < corePoolSize) {
              if (addWorker(command, true))
                  return;
              c = ctl.get();
          }
          if (isRunning(c) && workQueue.offer(command)) {
              int recheck = ctl.get();
              if (! isRunning(recheck) && remove(command))
                  reject(command);
              else if (workerCountOf(recheck) == 0)
                  addWorker(null, false);
          }
          else if (!addWorker(command, false))
              reject(command);
      }
  • submit() 用於提交需要返回值的任務(submit方法是ExecuorService接口中的方法,通過Execotors類初始化的線程池會返回一個ExecuorService 對象)

4.3 關閉

原理:遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程。所以無法響應中斷的的任務可能永遠無法終止。

  • shutdown

    shutdown只是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程。但是,此時線程池不會立刻退出,直到添加到線程池中的任務都已經處理完成,纔會退出。

       public void shutdown() {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                checkShutdownAccess();
                advanceRunState(SHUTDOWN); //將線程狀態設置爲SHUTDOWN
                interruptIdleWorkers(); // 中斷所有空間的線程
                onShutdown(); // hook for ScheduledThreadPoolExecutor
            } finally {
                mainLock.unlock();
            }
            tryTerminate();  // 讓線程狀態轉變爲terminated
        }
    
  • shutdownNow

    shutdownNow首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程(通過Thead.interrupt() ),並返回等待執行任務的列表。

      public List<Runnable> shutdownNow() {
            List<Runnable> tasks;
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                checkShutdownAccess();
                advanceRunState(STOP); //將線程狀態設置爲stop
                interruptWorkers(); //中斷所有線程(不一定能中斷所有)
                tasks = drainQueue(); //獲取未執行的線程
            } finally {
                mainLock.unlock();
            }
            tryTerminate();
            return tasks;
        }
    

只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true當所有的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至於應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用shutdown方法來關閉線程池,如果任務不一定要執行完,則可以調shutdownNow方法。

5 如何配置線程池

任務的性質:CPU密集型任務、IO密集型任務和混合型任務。

性質不同的任務可以用不同規模的線程池分開處理。CPU密集型任務應配置儘可能小的線程,如配置N cpu +1個線程的線程池。由於IO密集型任務線程並不是一直在執行任務,則應配置儘可能多的線程,如2*N cpu 。混合型的任務,如果可以拆分,將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐量將高於串行執行的吞吐量。如果這兩個任務執行時間相差太大,則沒必要進行分解。可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數。

建議使用有界隊列。有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點兒,比如幾千。

6 線程池監控

如果在系統中大量使用線程池,則有必要對線程池進行監控,方便在出現問題時,可以根據線程池的使用狀況快速定位問題。可以通過線程池提供的參數進行監控,在監控線程池的時候可以使用以下屬性。

  • ·taskCount:線程池需要執行的任務數量。
  • ·completedTaskCount:線程池在運行過程中已完成的任務數量,小於或等於taskCount。
  • ·largestPoolSize:線程池裏曾經創建過的最大線程數量。通過這個數據可以知道線程池是否曾經滿過。如該數值等於線程池的最大大小,則表示線程池曾經滿過。
  • ·getPoolSize:線程池的線程數量。如果線程池不銷燬的話,線程池裏的線程不會自動銷燬,所以這個大小隻增不減。
  • ·getActiveCount:獲取活動的線程數。

通過擴展線程池進行監控。可以通過繼承線程池來自定義線程池,重寫線程池的beforeExecute、afterExecute和terminated方法,也可以在任務執行前、執行後和線程池關閉前執行一些代碼來進行監控。例如,監控任務的平均執行時間、最大執行時間和最小執行時間等。這幾個方法在線程池裏是空方法。

7 一個小栗子

package threadpool;

import java.util.concurrent.*;

public class ExecutorsDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Thread t1 = new Thread(new RunnableThread(),"runnable thread 1");
        Thread t2 = new Thread(new RunnableThread(),"runnable thread 2");

        Callable<String> t3 = new CallableThread("callable thread 1");
        Callable<String> t4 = new CallableThread("callable thread 2");

        executor.execute(t1);
        executor.execute(t2);

        Future f1 = executor.submit(t3);
        Future f2 = executor.submit(t4);

        String result1 = (String) f1.get();
        String result2 = (String) f2.get();

        System.out.println("result 1 = "+result1);
        System.out.println("result 2 = "+result2);


        executor.shutdown();
        while (true){
            if(executor.isTerminated() == true){
                System.out.println("線程池關閉");
                break;
            }
        }



    }


}

class RunnableThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" is running and sleep 1s");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" is running and awake");
    }
}

class CallableThread implements Callable{

    private  String name;

    public CallableThread() {

    }

    public CallableThread(String name) {
        this.name = name;
    }

    @Override
    public Object call() throws Exception {

        System.out.println(Thread.currentThread().getName()+" (call) is running and sleep 1s");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" (call) is running and awake");

        return this.name +" is over";
    }
}

結果
在這裏插入圖片描述

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