线程池系列 - (3)拒绝策略

1,182

前言

本小节主要分析线程池的拒绝策略

系列文章

什么是拒绝策略

首先要明白,为什么线程池要有一个拒绝策略。也就是他出现的背景是什么。 了解过线程池的小伙伴应该都知道。线程池的构造参数中就有一个拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          //拒绝策略的接口
                          RejectedExecutionHandler handler) {

拒绝。意味着不满足某些条件,线程池也是一样。当线程数超过了maximumPoolSize的时候。就会拒绝添加任务。起到了保护线程池的作用

有哪些拒绝策略

RejectedExecutionHandler本身也是一个接口。如下

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

线程池本身提供了4中不同的拒绝策略

图片来源Java线程池实现原理及其在美团业务中的实践

AbortPolicy

可以看到。非常简单粗暴,直接抛出一个异常。

DiscardPolicy

嘿,啥也不管。啥也不问。任务拒绝就拒接了。也不给个提示啥的。就相当于直接把任务丢弃

DiscardOldestPolicy

如果线程池还在运行。那么就将阻塞队列中最前面的任务给取消,在执行当前任务 这么说有点玄乎。笔者写了一个简单的测试代码。能够更加描述清楚。笔者仿造DiscardOldestPolicy写了一个一摸一样的拒绝策略。然后加上打印。观察

完整的测试代码点击查看

public class Main {

    public static class CustomDiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public CustomDiscardOldestPolicy() {
        }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                System.out.println("被拒绝的任务 " + r.toString());
                Runnable runnable = e.getQueue().poll();
                if (runnable != null)
                    System.out.println("队列中拿到的任务 " + runnable.toString());
                else
                    System.out.println("队列中拿到的任务 null");
                e.execute(r);
            }
        }
    }

    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,
                2,
                60L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(2),
                new CustomDiscardOldestPolicy()
        );

        for (int i = 0; i < 5; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务执行---开始" + Thread.currentThread().getName() + " 任务 " + this.toString());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            System.out.println("添加任务 " + runnable.toString());
            pool.execute(runnable);
        }
    }
}

在上面的测试代码中。笔者将最大线程数控制在2个。核心线程数控制在1个。并且选择了一个有长度的队列ArrayBlockingQueue。设置其长度为2。

  • 当任务1添加进入以后。因为核心线程数是1.所以直接创建新的线程执行任务。
  • 当任务2添加进入以后。因为超过了核心线程数1.所以被添加到队列当中。此时的队列中有一个任务2
  • 当任务3添加进入以后。同理。被添加到队列当中。此时队列当中有两个任务了。
  • 当任务4添加进入以后。这个时候队列因为已经满了。所以判断是否超过了最大线程数。超过了就直接拒绝策略。

CallerRunsPolicy

默认的拒绝策略

在线程池的构造方法中可以看到 有一个defaultHandler拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

defaultHandler给我们创建了一个AbortPolicy。这也是线程池默认的策略。就是直接抛出异常

private static final RejectedExecutionHandler defaultHandler =
    new AbortPolicy();