Executors Java编程规范插件提示手动创建线程池的解决办法

一、背景

最近了解一下线程池,下载其中的代码并运行。

https://howtodoinjava.com/core-java/multi-threading/when-to-use-countdownlatch-java-concurrency-example-tutorial/

其中ApplicationStartupUtil这个类

 

package com.chujianyun;
import com.chujianyun.verifier.BaseHealthChecker;
import com.chujianyun.verifier.CacheHealthChecker;
import com.chujianyun.verifier.DatabaseHealthChecker;
import com.chujianyun.verifier.NetworkHealthChecker;
import java.util.concurrent.*;
public class ApplicationStartupUtil 
{
   private static BlockingQueue services;
   private static CountDownLatch latch;
   
   private ApplicationStartupUtil()
   {
   }
   
   private final static ApplicationStartupUtil INSTANCE = new ApplicationStartupUtil();
   
   public static ApplicationStartupUtil getInstance()
   {
      return INSTANCE;
   }
   
   public static boolean checkExternalServices() throws Exception
   {
      latch = new CountDownLatch(3);
      services = new ArrayBlockingQueue<>(3);
      services.add(new NetworkHealthChecker(latch));
      services.add(new CacheHealthChecker(latch));
      services.add(new DatabaseHealthChecker(latch));
      
      ExecutorService executorService = Executors.newFixedThreadPool(services.size());
      for(final Runnable v : services)
      {
            executorService.execute(v);
      }
      latch.await();

      for(final Runnable v : services)
      {
            BaseHealthChecker baseHealthChecker = (BaseHealthChecker) v;
         if( ! baseHealthChecker.isServiceUp())
         {
            return false;
         }
      }
      return true;
   }  
}

其中有下面代码:

ExecutorService executorService = Executors.newFixedThreadPool(services.size());

由于IDEA安装了阿里的Java编程规范检查插件,提示让手动创建线程池

Executors Java编程规范插件提示手动创建线程池的解决办法_第1张图片

二、探究

查看newFixedThreadPool函数源码:

/**
 * Creates a thread pool that reuses a fixed number of threads
 * operating off a shared unbounded queue.  At any point, at most
 * {@code nThreads} threads will be active processing tasks.
 * If additional tasks are submitted when all threads are active,
 * they will wait in the queue until a thread is available.
 * If any thread terminates due to a failure during execution
 * prior to shutdown, a new one will take its place if needed to
 * execute subsequent tasks.  The threads in the pool will exist
 * until it is explicitly {@link ExecutorService#shutdown shutdown}.
 *
 * @param nThreads the number of threads in the pool
 * @return the newly created thread pool
 * @throws IllegalArgumentException if {@code nThreads <= 0}
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue());
}

得知该函数最终调用的还是ThreadPoolExecutor构造方法。

因此上面一句可以改成:

int size = services.size();
ExecutorService executorService = new ThreadPoolExecutor(size,size,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

但是又有提示,建议要为线程池中的线程设置名称:

Executors Java编程规范插件提示手动创建线程池的解决办法_第2张图片

仅此在构造方法后加入TreadFactory,大功告成 

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
int size = services.size();
ExecutorService executorService = new ThreadPoolExecutor(size,size,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue(),namedThreadFactory);

补充

本文用到的  .ThreadFactoryBuilder 来自  guava 工具包

com.google.common.util.concurrent.ThreadFactoryBuilder



    com.google.guava
    guava
    27.0-jre

 

三、为什么要这么做呢?

我们参考阿里巴巴的Java开发手册内容:

8.   【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors各个方法的弊端:

1)    newFixedThreadPool和newSingleThreadExecutor:  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

2)    newCachedThreadPool和newScheduledThreadPool:  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

9. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

我在此简单进一步解读一下:

[1] newFixedThreadPool和newSingleThreadExecutor 由于最后一个参数即工作队列是:

    /**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }

链表类型的阻塞队列,而我们看其构造函数发现,默认队列大小是整数的最大值!!

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node(null);
    }

所以如果请求太多,队列很可能就耗费内存非常大导致OOM.

但是他们的线程数是固定的,而且一般不会太大,所以不会因为创建过多线程而导致OOM。

[2] newCachedThreadPool和newScheduledThreadPool:

    /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

其中第最大线程池大小是整数的最大值,因此线程可能不断创建,乃至到整数的最大值个线程,很容易导致OOM.

其中工作队列使用的是 SynchronousQueue

源码头部的注释中有说明

 * A {@linkplain BlockingQueue blocking queue} in which each insert
 * operation must wait for a corresponding remove operation by another
 * thread, and vice versa.  A synchronous queue does not have any
 * internal capacity, not even a capacity of one.  You cannot
 * {@code peek} at a synchronous queue because an element is only
 * present when you try to remove it; you cannot insert an element
 * (using any method) unless another thread is trying to remove it;
 * you cannot iterate as there is nothing to iterate.  The
 * head of the queue is the element that the first queued
 * inserting thread is trying to add to the queue; if there is no such
 * queued thread then no element is available for removal and
 * {@code poll()} will return {@code null}.  For purposes of other
 * {@code Collection} methods (for example {@code contains}), a
 * {@code SynchronousQueue} acts as an empty collection.  This queue
 * does not permit {@code null} elements.

可以看出

A {@linkplain BlockingQueue blocking queue} in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.

该类型的阻塞队列每一个插入操作必须等待对应的元素被另一个线程所移除,反之亦然。

因此阻塞队列不会无限拓展而导致OOM。

因此我们理解一些原则的时候,学习的时候多注重源码分析非常有必要,其他细节有待以后深入研究。

 

另外我写的一篇线程池导致线上故障的一篇文章大家也可以参考一下

https://blog.csdn.net/w605283073/article/details/89930497

 

如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。

 

你可能感兴趣的:(Java基础,问题积累)