Bootstrap

线程池拒绝策略(超详细讲解)

共用四种拒绝策略分别为: AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy 它们都是 ThreadPoolExecutor 类下的静态内部类,并都实现了 RejectExecutionHandler 接口

下面的 MyRunnable 类用来描述一个任务  

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在执行 " + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

1. AbortPolicy:

触发该拒绝策略时,会直接抛出 RejectedExecutionException 异常;

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.AbortPolicy());
        MyRunnable runnable1 = new MyRunnable();
        MyRunnable runnable2 = new MyRunnable();
        MyRunnable runnable3 = new MyRunnable();
        executor.execute(runnable1);
        executor.execute(runnable2);
        executor.execute(runnable3);
    }

抛出异常之后,此时新的任务也无法继续完成了,但是在阻塞队列中存在的任务并非是新的任务;所以等待 2 秒(因为 MyRunnable 接口 重写的 run 方法 sleep 了 2 秒)之后,此时线程会继续执行阻塞队列中的任务;看下面这个情况:

        Thread.sleep(3000);
        System.out.println(System.currentTimeMillis());
        MyRunnable runnable4 = new MyRunnable();
        executor.execute(runnable4);

在上述代码的基础上,再添加以上代码,再次执行程序:

可以看到,本来应该是执行完阻塞队列的任务之后,主线程休眠 3 秒,执行阻塞队列的任务会休眠2 秒,所以主线程休眠 3 秒之后,线程应该是空闲状态,理应继续执行下面的逻辑,打印当前时间戳,并执行任务 4,但是使用的是 AbortPolicy 拒绝策略,抛出异常后,后续任务也不会执行了;

2. CallerRunsPolicy:

触发该拒绝策略时,添加的任务由添加任务的线程执行;

public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.CallerRunsPolicy());
        MyRunnable runnable1 = new MyRunnable();
        MyRunnable runnable2 = new MyRunnable();
        MyRunnable runnable3 = new MyRunnable();
        executor.execute(runnable1);
        executor.execute(runnable2);
        executor.execute(runnable3);
}

由执行结果可以看到,第一个任务由线程池的核心线程执行, 第二个任务进入阻塞队列并等待执行,第三个任务会触发拒绝策略 CallerRunsPolicy,由添加任务的线程执行,显然此处是 main 线程添加的任务,故由 main 线程执行,核心线程经过 2 秒执行完毕后,继续执行阻塞队列中的任务;此时,如果有新任务再次添加,结果又会怎么样呢?同样添加上述代码,测试结果如下:

        Thread.sleep(3000);
        System.out.println(System.currentTimeMillis());
        MyRunnable runnable4 = new MyRunnable();
        executor.execute(runnable4);

可以看出,触发 CallerRunsPolicy 拒绝策略之后,再有新任务来时,仍然能正常执行;

3. DiscardOldestPolicy:

触发该拒绝策略时,会丢弃一个阻塞队列中最古老的任务,并将新的任务加入到阻塞队列中;

为了验证该拒绝策略,对 MyRunnable 类添加一个属性 name,并对 run 方法进行调整:

class MyRunnable implements Runnable{
    private String name;
    public MyRunnable(String name){
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在执行 " + this.name + " 时间 " + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardOldestPolicy());
        MyRunnable runnable1 = new MyRunnable("第一个任务");
        MyRunnable runnable2 = new MyRunnable("第二个任务");
        MyRunnable runnable3 = new MyRunnable("第三个任务");
        MyRunnable runnable4 = new MyRunnable("第四个任务");

        executor.execute(runnable1);
        executor.execute(runnable2);
        executor.execute(runnable3);
        executor.execute(runnable4);

        Thread.sleep(3000);
        System.out.println("所有任务执行完毕");
    }

由代码及运行结果可以看出,首先第一个任务来时,由核心线程执行,第二个任务来时,进入阻塞队列,第三个任务来时,线程全部都不空闲,并且阻塞队列已满,故会触发拒绝策略,丢弃第二个任务,并将第三个任务放入阻塞队列,第四个任务来时,仍然会触发拒绝策略,丢弃第三个任务,并将第四个任务放入阻塞队列,之后核心线程执行完第一个任务之后,会继续执行阻塞队列中的第四个任务;

4. DiscardPolicy:

触发该拒绝策略时,会直接丢弃掉新来的任务,即直接忽略;

public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy());
        MyRunnable runnable1 = new MyRunnable("第一个任务");
        MyRunnable runnable2 = new MyRunnable("第二个任务");
        MyRunnable runnable3 = new MyRunnable("第三个任务");

        executor.execute(runnable1);
        executor.execute(runnable2);
        executor.execute(runnable3);

        Thread.sleep(3000);
        System.out.println("所有任务执行完毕");
    }

由代码及运行结果可以看出,首先第一个任务来时,由核心线程执行,第二个任务来时,进入阻塞队列,第三个任务来时,会触发 DiscardPolicy 拒绝策略,直接丢弃第三个任务,等待核心线程执行完第一个任务之后,会继续执行阻塞队列中的第二个任务;

;