天天看點

JAVA中常見的線程池

​Executor​

​​架構最核心的類是ThreadPoolExecutor,它是線程池的實作類,詳情請看​​ThreadPoolExecutor 源碼解析​​​。​

​Executors​

​​還為我們提供了4種常見的線程池,他們都是基于ThreadPoolExecutor而來,還有一個是​

​WorkStealingPool​

​是基于ForkJoinPool的。

FixedThreadPool

使用方式

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);      

建立一個工作線程數為10的線程池。

實作

public static ExecutorService newFixedThreadPool(int nThreads) {
  return new ThreadPoolExecutor(nThreads, nThreads,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>());
}      

我們可以看到:

  • 它其實就是建立了一個核心線程數(corePoolSize)和最大線程數(maximumPoolSize)都是​

    ​nThreads​

    ​​的​

    ​ThreadPoolExecutor​

  • ​keepAliveTime​

    ​為0表示線程一旦空閑就會被回收
  • 它使用了​

    ​LinkedBlockingQueue​

    ​​隊列來存儲任務,但是該隊列的長度預設是​

    ​Integer.MAX_VALUE​

    ​長度,當任務過多時有可能會發生記憶體溢出,是以呢不建議使用。

運作示意圖

JAVA中常見的線程池

SingleThreadExecutor

使用方式

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();      

建立一個工作線程數為1的線程池。

實作

public static ExecutorService newSingleThreadExecutor() {
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()));
}      

我們可以看到:

  • 它其實就是建立了一個核心線程數(corePoolSize)和最大線程數(maximumPoolSize)都是​

    ​1​

    ​​的​

    ​ThreadPoolExecutor​

  • ​keepAliveTime​

    ​為0表示線程一旦空閑就會被回收
  • 它也使用了​

    ​LinkedBlockingQueue​

    ​​隊列來存儲任務,但是該隊列的長度預設是​

    ​Integer.MAX_VALUE​

    ​長度,當任務過多時有可能會發生記憶體溢出,是以呢不建議使用。
SingleThreadExecutor會将送出的任務按照送出順序串行執行

運作示意圖

JAVA中常見的線程池

CachedThreadPool

使用方式

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();      

建立一個最大工作線程數是​

​Integer.MAX_VALUE​

​的線程池。

實作

public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                  60L, TimeUnit.SECONDS,
                  new SynchronousQueue<Runnable>());
}      

我們可以看到:

  • 建立了一個核心線程數(corePoolSize)是0,最大線程數(maximumPoolSize)是​

    ​Integer.MAX_VALUE​

    ​​的​

    ​ThreadPoolExecutor​

    ​,該線程池的線程數不可控,極端情況下, 可能會因為建立過多線程而耗盡CPU和記憶體資源,不建議使用
  • 空閑線程的存活時間是1分鐘
  • 它也使用了​

    ​SynchronousQueue​

    ​隊列來存儲任務,該隊列預設不存儲元素,每一個put操作必須等待一個take操作,否則不能添加元素。當一直有任務送出的時候,改線程池會不斷的新增工作線程來執行任務

運作示意圖

JAVA中常見的線程池

前面提到過,SynchronousQueue是一個沒有容量的阻塞隊列。每個插入操作必須等待另一個線程的對應移除操作,反之亦然。CachedThreadPool使用SynchronousQueue,把主線程送出的任務傳遞給空閑線程執行。CachedThreadPool中任務傳遞的示意圖如圖:

JAVA中常見的線程池

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor。它主要用來在給定的延遲之後運作任務,或者定期執行任務。ScheduledThreadPoolExecutor的功能與Timer類似,但 ScheduledThreadPoolExecutor功能更強大、更靈活。

使用方式

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);

scheduledThreadPool.scheduleAtFixedRate(() -> {
    System.out.println(Thread.currentThread().getName() + "   " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    //0表示首次執行任務的延遲時間,1表示每次執行任務的間隔時間,TimeUnit.SECONDS執行的時間間隔數值機關
}, 0, 1, TimeUnit.SECONDS);      

建立一個核心線程時10,最大工作線程數是​

​Integer.MAX_VALUE​

​的線程池。

運作結果:

Connected to the target VM, address: '127.0.0.1:50560', transport: 'socket'
pool-4-thread-1   2019-08-06T15:57:08.942
pool-4-thread-1   2019-08-06T15:57:09.802
pool-4-thread-2   2019-08-06T15:57:10.803
pool-4-thread-1   2019-08-06T15:57:11.803
pool-4-thread-3   2019-08-06T15:57:12.803
pool-4-thread-2   2019-08-06T15:57:13.803      

實作

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
  return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
      new DelayedWorkQueue());
}      

我們可以看到:

  • 建立了一個核心線程數(corePoolSize)是10,最大線程數(maximumPoolSize)是​

    ​Integer.MAX_VALUE​

    ​​的​

    ​ThreadPoolExecutor​

    ​,該線程池的線程數不可控,極端情況下, 可能會因為建立過多線程而耗盡CPU和記憶體資源,不建議使用
  • 空閑線程的存活時間是0,一但空閑就會被回收掉
  • 它也使用了​

    ​DelayedWorkQueue​

    ​​隊列來存儲任務,它是一個隻能存儲​

    ​RunnableScheduledFuture​

    ​​任務的延遲隊列,一般我們使用他的子類​

    ​ScheduledFutureTask​

    ​。

ScheduledFutureTask類圖

JAVA中常見的線程池

ScheduledFutureTask核心屬性

/** 表示這個任務被添加到ScheduledThreadPoolExecutor中的序号 */
private final long sequenceNumber;

/** 表示這個任務将要被執行的具體時間 */
private long time;

/** 表示任務執行的間隔周期 */
private final long period;      

運作示意圖

JAVA中常見的線程池

WorkStealingPool

使用方式

ExecutorService workStealingPool = Executors.newWorkStealingPool();      

建立一個ForkJoinPool線程池,線程池預設大小是CPU核心數。

實作

public static ExecutorService newWorkStealingPool() {
  return new ForkJoinPool
    (Runtime.getRuntime().availableProcessors(),
     ForkJoinPool.defaultForkJoinWorkerThreadFactory,
     null, true);
}      

我們可以看到:

  • 它其實就是建立了一個核心線程數是CPU核心數的​

    ​ForkJoinPool​

總結

從上面我們可以看到,大部分的線程池是不建議直接使用的,我們在使用線程池的時候盡量使用​

​ThreadPoolExecutor​

​​來建立,并且根據業務合理的設定​

​corePoolSize​

​​、​

​maximumPoolSize​

​​、​

​keepAliveTime​

​​、​

​BlockingQueue​

​​、​

​RejectedExecutionHandler​

​的值,否則線上上環境很容易耗盡CPU和記憶體資源,導緻服務不可用。

參考

《java并發程式設計的藝術》

源碼

​​https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases​​

layering-cache