Java 线程池原理解析(三)

本文探讨了线程池中的线程饥饿死锁问题,分析了不同线程池类型及饱和策略的选择,如AbortPolicy、CallerRunsPolicy等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用线程池的过程中,有一个很有必要考虑的问题就是线程饥饿死锁问题
先前我们考虑的放入线程池中的各个task都是相互独立的,这样我们就把问题简单化了。但是真实情况中还会出现一个任务依赖于另一个任务的完成。
现在假设一种条件,我们使用单线程的Executor,如果一个任务将另一个任务提交到同一个Executor,并且等待这个任务的执行结果。那么就会因为单线程的Executor的串行机制导致死锁的发生。
第二个任务停留在队列中等待第一个任务的完成,而第一个任务又无法完成,因为它在等待第二个任务的完成。
在更大的线程池当中,如果所有正在执行任务的线程都由于等待其他处于工作队列中的任务而阻塞,那么仍然会发生同样的问题。这种现象被称为线程饥饿死锁(Thread Startvation Deadlock)。
假如某个任务等待另一个任务的执行结果,那么除非线程池足够大否则将发生线程饥饿死锁

现在回想一下上一篇中提到的创建四种不同的线程池。到底哪种线程池最适合用来处理线程饥饿死锁的情况呢?

只有当任务相互独立时,为线程池或工作队列设置界限才是最合理的。如果任务之间存在依赖性,那么有界的线程池或队列就可能导致线程”饥饿”死锁问题。此时应该使用无界的线程池,例如newCachedThreadPool

提到了无界,必然要解释一下有界。
newFixedThreadPool和newSingleThreadExecutor在默认情况下将使用一个无界的LinkedBlockingQueue。如果task到达的速度超过了线程池执行task的速度,那么队列将会无限制的增加。这显然会导致服务器过载的情况。
于是一种更稳妥的资源管理策略是使用有界队列。比如有界的LinkedBlockingQueue、PriorityBlockingQueue。有界队列有助于避免资源耗尽的情况发生,但是它又带来的新的问题:当队列被填满后,新的任务该怎么办?
下面就是要谈到的重点:饱和策略。
还记得我们上一篇提到的handler吗?其实这就是实现饱和策略的关键RejectedExecutionHandler。
JDK中提供了几种不同的RejectedExecutionHandler实现,每种实现包含有不同的饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。

  • “中止(Abort)策略是默认的饱和策略”,该策略是讲抛出未检查的RejectedExecutionException,调用者可以捕获这个异常,然后根据需求编写自己的处理代码。
/**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
  • “抛弃(Discard)”策略会悄悄抛弃该任务。
/**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @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) {
        //果然是悄悄抛弃
        }
    }
  • “抛弃最旧的(DiscardOldest)”策略则会抛弃下一个将被执行的任务,然后尝试重新提交新的任务(如果工作队列是一个优先队列,那么 DisacrdOldest将导致抛弃优先级最高的任务,因此最好不要将DiscardOldest与优先级队列放在一起使用)
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * 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()) {
                e.getQueue().poll();//取队列头
                e.execute(r);//再次尝试执行Runnable r
            }
        }
    }
  • “调用者运行(CallerRuns)”实现了一种调节机制,它既不会抛弃任务,也不会抛出异常,而是将某些任务返回给调用者,从而降低新任务的流量。它不会再线程池中的某个线程中执行新提交的任务,而是在一个调用了execute的线程中执行该任务。也就是说当线程池中的所有线程都被占用并且工作队列也被填满后,下一个任务会在调用execute时在主线程中执行。由于执行任务需要一定的时间,因此主线程在一段时间内不能提交任何的新任务,从而使得工作线程有时间来处理正在执行的任务。在这段期间内,主线程不会调用accept,因此到达的请求将被保存在TCP层的队列中而不是在应用层队列中。
    如果新的任务请求持续到达,TCP层会发现它的任务队列被填满,因此同样会开始抛弃请求。当服务器过载时,这种过载情况会由内向外蔓延开来。由线程池-工作队列-应用程序-TCP层-客户端。导致服务器在高负载下实现一种平缓的性能降低。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is 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()) {
                r.run();
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值