【线程池】线程池的拒绝策略(饱和策略)

目录

一、总结

二、拒绝策略源码分析

2.1 AbortPolicy

2.2 DiscardPolicy

2.3 DiscardOldestPolicy

2.4 CallerRunsPolicy

2.5 自定义

三、执行拒绝策略


一、总结

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略:

  • ThreadPoolExecutor.AbortPolicy: 抛出 RejectedExecutionException来拒绝新任务的处理。
  • ThreadPoolExecutor.CallerRunsPolicy: 调用执行自己的线程运行任务,也就是直接在【调用execute方法的线程中】运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
  • ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
  • ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃队列中最早的未处理的任务请求,然后再尝试将新任务加入到队列中。

举个例子:Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略来配置线程池的时候,默认使用的是 AbortPolicy。在这种饱和策略下,如果队列满了,ThreadPoolExecutor 将抛出 RejectedExecutionException 异常来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。如果不想丢弃任务的话,可以使用CallerRunsPolicy。CallerRunsPolicy 和其他的几个策略不同,它既不会抛弃任务,也不会抛出异常,而是将任务回退给调用者,使用调用者的线程来执行任务

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            // 直接主线程执行,而不是线程池中的线程执行
            r.run();
        }
    }
}

二、拒绝策略源码分析

RejectedExecutionHandler类型

RejectedExecutionHandler

特性及效果

AbortPolicy

线程池默认的策略,如果元素添加到线程池失败,会抛出RejectedExecutionException异常

DiscardPolicy

如果添加失败,则放弃,并且不会抛出任何异常

DiscardOldestPolicy

如果添加到线程池失败,会将队列中最早添加的元素移除,再尝试添加,如果失败则按该策略不断重试

CallerRunsPolicy

如果添加失败,那么主线程会自己调用执行器中的execute方法来执行改任务

自定义

如果觉得以上几种策略都不合适,那么可以自定义符合场景的拒绝策略。需要实现RejectedExecutionHandler接口,并将自己的逻辑写在rejectedExecution方法内。

2.1 AbortPolicy

该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。

源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    //不做任何处理,直接抛出异常
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}

2.2 DiscardPolicy

这个策略和AbortPolicy的slient版本,如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。

源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    //就是一个空的方法
}

2.3 DiscardOldestPolicy

这个策略从字面上也很好理解,丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试将新任务加入队列。

因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队。

源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        //移除队头元素
        e.getQueue().poll();
        //再尝试入队
        e.execute(r);
    }
}

2.4 CallerRunsPolicy

使用此策略,如果添加到线程池失败,那么主线程(调用线程池执行任务的线程)会自己去执行该任务,不会等待线程池中的线程去执行。就像是个急脾气的人,我等不到别人来做这件事就干脆自己干。

源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        //直接执行run方法
        r.run();
    }
}

2.5 自定义

如果以上策略都不符合业务场景,那么可以自己定义一个拒绝策略,只要实现RejectedExecutionHandler接口,并且实现rejectedExecution方法就可以了。具体的逻辑就在rejectedExecution方法里去定义就OK了。

例如:我定义了我的一个拒绝策略,叫做MyRejectPolicy,里面的逻辑就是打印处理被拒绝的任务内容

public class MyRejectPolicy implements RejectedExecutionHandler{
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        //Sender是我的Runnable类,里面有message字段
        if (r instanceof Sender) {
            Sender sender = (Sender) r;
            //直接打印
            System.out.println(sender.getMessage());
        }
    }
}

这几种策略没有好坏之分,只是适用不同场景,具体哪种合适根据具体场景和业务需要选择,如果需要特殊处理就自己定义好了。

三、执行拒绝策略

以ThreadPoolExecutor为例,它是通过自己的reject()方法来执行拒绝策略的。

// 要针对任务command执行拒绝策略
final void reject(Runnable command) {
    // handler就是核心参数中传入的拒绝策略
    // 如果创建线程池时没有向构造方法中传入拒绝策略,那么默认的拒绝策略就是抛出异常
    handler.rejectedExecution(command, this);
}

相关文章:【线程池】Java的线程池
                  【线程池】Java线程池的核心参数
                  【线程池】Executors框架创建线程池                            【线程池】ScheduledExecutorService接口和ScheduledThreadPoolExecutor定时任务线程池使用详解

猜你喜欢

转载自blog.csdn.net/cy973071263/article/details/131484329