【阿里开发规范】ThreadPoolExecutor自定义线程池示例(亲测)

核心内容导读

  1. 简单讲解为什么阿里不推荐使用Executors创建线程池
  2. 自定义线程池的几大基本属性含义
  3. 并发不高、任务执行时间长的业务如何创建自定义线程池(实例)

一、为什么阿里规范强制使用ThreadPoolExecutor?

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool SingleThreadPool
允许的请求队列的长度可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

我们可以通过自定义线程池去规避这些问题。

1.SingleThreadExecutor源码分析

源码如下:

/**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • 根据源码可以看出,SingleThreadExecutor的核心线程数=最大线程数=1,非核心线程空闲存活时间=0,存放任务的队列是默认Integer.MAX_VALUE(无界)的LinkedBlockingQueue,它的拒绝策略是默认的 AbortPolicy (丢弃任务并抛出RejectedExecutionException)。
  • 它只会用唯一的工作线程来执行任务,即单线程化的线程池,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • 但由于它的队列无界,新的任务会不断放入队列中,队列是一种数据结构,会占用内存空间,所以SingleThreadExecutor会造成工作任务过多导致内存溢出(OOM)的情况。

2.CachedThreadPool源码分析

源码如下:

 /**
     * 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<Runnable>());
    }
  • 根据源码可以看出,CachedThreadPool的核心线程数=0,最大线程数是无界的,非核心线程空闲存活时间=60s,存放任务的队列是不存储任务的SynchronousQueue,它的拒绝策略是默认的 AbortPolicy (丢弃任务并抛出RejectedExecutionException)。
  • 由于核心线程数=0,且为SynchronousQueue,故CachedThreadPool线程池 无界的非核心线程可灵活回收空闲线程,若无可回收,则无限创建线程,直至OOM。
  • 但由于它的非核心线程数无界,过多的新任务会导致不断创建新的线程,所以CachedThreadPool会造成CPU一直在运行不同线程,导致CPU100%。

3. FixedThreadPool源码分析

源码如下:

  /**
     * 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<Runnable>());
    }
  • 根据源码可以看出,FixedThreadPool的核心线程数和最大线程数都是可自定义,非核心线程空闲存活时间0s,存放任务的队列是默认Integer.MAX_VALUE(无界)的LinkedBlockingQueue,它的拒绝策略是默认的 AbortPolicy (丢弃任务并抛出RejectedExecutionException)。
  • 固定了N个线程虽然可以防止无限创建新的线程,但提交给线程池的任务队列是不限制大小的,即它的队列无界,新的任务会不断放入队列中,队列是一种数据结构,会占用内存空间,所以FixedThreadPool会造成工作任务过多导致内存溢出(OOM)的情况。

一次Java线程池误用(newFixedThreadPool)引发的线上血案和总结

4. ScheduledThreadPoolExecutor源码分析

源码如下:

    /**
     * Creates a thread pool that can schedule commands to run after a
     * given delay, or to execute periodically.
     * @param corePoolSize the number of threads to keep in the pool,
     * even if they are idle
     * @return a newly created scheduled thread pool
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
  /**
     * Creates a new {@code ScheduledThreadPoolExecutor} with the
     * given core pool size.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
	// 数组的初始容量为16 设置为16的原因跟hashmap中数组容量为16的原因一样
	private static final int INITIAL_CAPACITY = 16;
	// 用于记录RunableScheduledFuture任务的数组
	private RunnableScheduledFuture<?>[] queue =
	            new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
	private final ReentrantLock lock = new ReentrantLock();
	// 当前队列中任务数,即队列深度
	private int size = 0;
	// leader线程用于等待队列头部任务,
	private Thread leader = null;
	// 当线程成为leader时,通知其他线程等待
	private final Condition available = lock.newCondition();

  • ScheduledThreadPoolExecutor使用场景:定时任务,要求时间误差小,稳定。
  • 正常情况下,定时器我们都是用Timer和TimerTask这两个类就能完成定时任务,并且设置延长时间和循环时间间隔。
    ScheduledThreadPoolExecutor也能完成Timer一样的定时任务,并且时间间隔更加准确。
    定时任务ScheduledThreadPoolExecutor的使用详解
  • 根据源码可以看出,ScheduledThreadPoolExecutor的核心线程数可自定义,最大线程数是Integer.MAX_VALUE,非核心线程存活时间0s,存放任务的队列是定制的优先级队列, 只能用来存储RunnableScheduledFutures任务的DelayedWorkQueue,有OOM风险。它的拒绝策略是默认的 AbortPolicy (丢弃任务并抛出RejectedExecutionException)。
  • scheduleAtFixedRate的jdk文档中:如果在任务的执行中遇到异常,后续执行被取消。

1.ScheduledThreadPoolExecutor遇到的坑
2.理解优先队列DelayedWorkQueue


二、自定义线程池的几大基本属性含义

1.ThreadPoolExecutor源码分析

源码如下:

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @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.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @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}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize:核心线程个数,如果不设置 allowCoreThreadTimeOut 会保持一直在线程池中,即使是空闲状态。
  • maximumPoolSize :线程池中最大线程数。
  • keepAliveTime:当前所需运行线程数 > 核心线程数时,非核心线程空闲状态等待被分配任务的最大时间,超过该时间后非核心线程将被回收。
  • unit:keepAliveTime 的时间单位
  • workQueue:当线程池所有线程都在工作时,新的任务会被放到该队列中等待被分配。这个队列将只保存实现 Runnable 接口,并通过 execute 方法提交的任务。
  • threadFactory:执行器创建新线程时使用的工厂,ThreadPoolExecutor默认的工厂为 DefaultThreadFactory
  • handler:当工作任务数 > 最大线程数+队列数 时,新的任务的拒绝策略,默认为AbortPolicy

2.threadFactory选型介绍

  • DefaultThreadFactory代码如下:
    /**
     * The default thread factory
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

  • 源码图示:
    在这里插入图片描述

3.handler(拒绝策略)选型介绍

  1. AbortPolicy 中止策略:丢弃任务并抛出RejectedExecutionException
  2. CallerRunsPolicy 调用者运行:只要线程池未关闭,用调用者所在的线程来执行当前新任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
  3. DiscardOldestPolicy 抛弃旧任务策略丢弃队列中最旧的一个任务(即将被执行的一个任务),并执行最新的任务,如果执行器被关闭则任务被丢弃。–喜新弃旧型
  4. DiscardPolicy 抛弃策略丢弃任务,不抛出异常,不做任何操作。

4.workQueue选型介绍

  1. ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;有界存储多余任务的队列。有效防止OOM
    一个 ArrayBlockingQueue 不当使用,导致公司损失几百万!
  2. LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;可以人为指定队列大小。默认接近无界,可以设置为有界的,可存储多余任务的队列。无界时会造成OOM。
  3. SynchronousQueue:SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。拥有公平(FIFO)和非公平(LIFO)策略,非公平策略会导致一些数据永远无法被消费的情况。如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。只有在使用无界线程池或者有饱和策略时才建议使用该队列。使用SynchronousQueue要避免线程拒绝执行操作。不会OOM,但可能造成CPU长时间被占用,CPU100%。
  4. PriorityBlockingQueue:通过二叉小顶堆实现,可以用一棵完全二叉树表示。有优先级的无界阻塞队列,默认情况下元素采用自然顺序升序排列,也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。但需要注意的是不能保证同优先级元素的顺序。
    PriorityBlockingQueue深度解析
  5. DelayedWorkQueue: DelayedWorkQueue核心数据结构是二叉最小堆的优先队列,队列满时会自动扩容,所以offer操作永远不会阻塞,maximumPoolSize也就用不上了,所以线程池中永远会保持至多有corePoolSize个工作线程正在运行
    Queue常用类解析之BlockingQueue(一):PriorityBlockingQueue、DelayQueue和DelayedWorkQueue
  6. BlockingQueue核心方法描述:
    在这里插入图片描述

三、并发不高、任务执行时间长,不接收返回值的业务创建自定义线程池实例

  1. 业务场景:
    由于接口处理时间较长,所以将耗时部分设计为异步执行,主线程先返回成功请求状态码,子线程继续执行,且主线程无需等待子线程返回结果。

  2. 实现逻辑:

	//创建线程池,异步执行SynchronousQueue选型防止OOM,拒绝策略用默认AbortPolicy,不丢弃任务,失败时抛异常,最大10个防止线程过多
			ExecutorService threadPool=new ThreadPoolExecutor(0, 10,
					60L, TimeUnit.SECONDS,
					new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
			threadPool.execute(() -> {
			//子线程异步执行的代码块,无返回值
			});
			//关闭线程池
			threadPool.shutdown();

自定义线程池属性:异步执行SynchronousQueue选型防止OOM,拒绝策略用默认AbortPolicy,不丢弃任务,失败时抛异常,最大10个防止线程过多。

  • 9
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java中,我们可以通过自定义ThreadPoolExecutor类来创建自己的线程池ThreadPoolExecutor类是ExecutorService接口的一个实现,它提供了一个灵活的线程池管理机制。 要自定义ThreadPoolExecutor线程池,我们需要使用ThreadPoolExecutor类的构造函数来创建一个实例,并设置一些参数来配置线程池的行为。下面是一个示例代码: ```java import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class CustomThreadPoolExecutor { public static void main(String[] args) { // 创建一个阻塞队列,用于存放待执行的任务 BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10); // 创建自定义线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 1, // 空闲线程存活时间 TimeUnit.MINUTES, // 时间单位 queue // 阻塞队列 ); // 添加任务到线程池中 for (int i = 0; i < 20; i++) { executor.execute(() -> { System.out.println("执行任务"); }); } // 关闭线程池 executor.shutdown(); } } ``` 在上面的示例代码中,我们使用了一个LinkedBlockingQueue作为阻塞队列来存放待执行的任务。然后,我们创建了一个ThreadPoolExecutor实例,并设置了核心线程数为5,最大线程数为10,空闲线程存活时间为1分钟。接下来,我们通过execute方法向线程池提交了20个任务。 最后,记得要调用executor.shutdown()方法来关闭线程池,以确保所有任务执行完毕并释放资源。 通过自定义ThreadPoolExecutor类,我们可以根据实际需求来设置线程池的参数,并且可以根据需要灵活地处理提交的任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值