专注Java教育14年 全国咨询/投诉热线:400-8080-105
动力节点LOGO图
始于2009,口口相传的Java黄埔军校
首页 hot资讯 线程池拒绝策略详解

线程池拒绝策略详解

更新时间:2021-02-01 17:39:34 来源:动力节点 浏览1247次

线程池工作中,当任务量很大,超过系统实际承载能力时,如果不去处理,系统很可能崩溃,所以JDK内置提供了4种线程池拒绝策略,可以合理解决这种问题。除此之外,还有第三方实现的4种拒绝策略。当线程池中线程已用完不能再创建,等待队列也排满,如果此时再有新任务,就会触发执行拒绝策略之一。

一、JDK内置的4种拒绝策略

1.CallerRunsPolicy(调用者运行策略)

一般在不允许失败的、对性能要求不高、并发量较小的场景下使用,因为线程池一般情况下不会关闭,也就是提交的任务一定会被运行,但是由于是调用者线程自己执行的,当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。

2.AbortPolicy(中止策略)

当触发拒绝策略时,它就会直接抛出拒绝执行的异常,中止策略就是直接打断当前执行的流程。它没有特殊的使用场景,但是一点要正确处理抛出的异常。ThreadPoolExecutor中默认的策略就是AbortPolicy,ExecutorService接口的系列ThreadPoolExecutor因为都没有显示的设置拒绝策略,所以默认的都是这个。但是请注意,ExecutorService中的线程池实例队列都是无界的,也就是说把内存撑爆了都不会触发拒绝策略。当自己自定义线程池实例时,使用这个策略一定要处理好触发策略时抛的异常,因为他会打断当前的执行流程。

3.DiscardPolicy(丢弃策略)

如果你提交的任务无关紧要,你就可以使用它 。因为它就是个空实现,会悄无声息的吞噬你的的任务。它就是直接静悄悄的丢弃这个任务,不触发任何动作。所以这个策略基本上不用了。

4.DiscardOldestPolicy(弃老策略)

这个策略依然会丢弃任务,丢弃时也是无声无息,但丢弃的是老的未执行的任务,而且是待执行优先级较高的任务。基于这个特性,我能想到的场景就是,发布消息,和修改消息,当消息发布出去后,还未执行,此时更新的消息又来了,这时未执行的消息的版本比现在低就可以被丢弃了。因为队列中还可能存在消息版本更低的消息会排队执行,所以在真正处理消息的时候一定要做好消息的版本比较。此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。

二、第三方实现的拒绝策略

1.dubbo中的线程拒绝策略

public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {



    protected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class);



    private final String threadName;



    private final URL url;



    private static volatile long lastPrintTime = 0;



    private static Semaphore guard = new Semaphore(1);



    public AbortPolicyWithReport(String threadName, URL url) {

        this.threadName = threadName;

        this.url = url;

    }



    @Override

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

        String msg = String.format("Thread pool is EXHAUSTED!" +

                        " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," +

                        " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",

                threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),

                e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),

                url.getProtocol(), url.getIp(), url.getPort());

        logger.warn(msg);

        dumpJStack();

        throw new RejectedExecutionException(msg);

    }



    private void dumpJStack() {

       //省略实现

    }

}

可以看到,当dubbo的工作线程触发了线程拒绝后,主要做了三个事情,原则就是尽量让使用者清楚触发线程拒绝策略的真实原因。输出了一条警告级别的日志,日志内容为线程池的详细设置参数,以及线程池当前的状态,还有当前拒绝任务的一些详细信息。可以说,这条日志,使用dubbo的有过生产运维经验的或多或少是见过的,这个日志简直就是日志打印的典范,其他的日志打印的典范还有spring。得益于这么详细的日志,可以很容易定位到问题所在。

2.Netty中的线程池拒绝策略

private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {

        NewThreadRunsPolicy() {

            super();

        }



        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

            try {

                final Thread t = new Thread(r, "Temporary task executor");

                t.start();

            } catch (Throwable e) {

                throw new RejectedExecutionException(

                        "Failed to start a new thread", e);

            }

        }

}

Netty中的实现很像JDK中的CallerRunsPolicy,舍不得丢弃任务。不同的是,CallerRunsPolicy是直接在调用者线程执行的任务。而 Netty是新建了一个线程来处理的。所以,Netty的实现相较于调用者执行策略的使用面就可以扩展到支持高效率高性能的场景了。但是也要注意一点,Netty的实现里,在创建线程时未做任何的判断约束,也就是说只要系统还有资源就会创建新的线程来处理,直到new不出新的线程了,才会抛创建线程失败的异常。

3.activeMq中的线程池拒绝策略

new RejectedExecutionHandler() {

                @Override

                public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {

                    try {

                        executor.getQueue().offer(r, 60, TimeUnit.SECONDS);

                    } catch (InterruptedException e) {

                        throw new RejectedExecutionException("Interrupted waiting for BrokerService.worker");

                    }



                    throw new RejectedExecutionException("Timed Out while attempting to enqueue Task.");

                }

            });

activeMq中的策略属于最大努力执行任务型,当触发拒绝策略时,在尝试一分钟的时间重新将任务塞进任务队列,当一分钟超时还没成功时,就抛出异常。

4.pinpoint中的线程池拒绝策略

public class RejectedExecutionHandlerChain implements RejectedExecutionHandler {

    private final RejectedExecutionHandler[] handlerChain;



    public static RejectedExecutionHandler build(List chain) {

        Objects.requireNonNull(chain, "handlerChain must not be null");

        RejectedExecutionHandler[] handlerChain = chain.toArray(new RejectedExecutionHandler[0]);

        return new RejectedExecutionHandlerChain(handlerChain);

    }



    private RejectedExecutionHandlerChain(RejectedExecutionHandler[] handlerChain) {

        this.handlerChain = Objects.requireNonNull(handlerChain, "handlerChain must not be null");

    }



    @Override

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        for (RejectedExecutionHandler rejectedExecutionHandler : handlerChain) {

            rejectedExecutionHandler.rejectedExecution(r, executor);

        }

    }

}

pinpoint的拒绝策略实现很有特点,和其他的实现都不同。他定义了一个拒绝策略链,包装了一个拒绝策略列表,当触发拒绝策略时,会将策略链中的rejectedExecution依次执行一遍。

以上就是线程池拒绝策略的详细介绍,我们学习线程池拒绝策略的最终目的是可以根据不同的使用场景灵活运用各种拒绝策略。在本站的多线程教程中,对线程池拒绝策略还有更加独到的见解和分析,感兴趣的小伙伴不要错过哦。

 

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>