问:如果线程池中所有的线程都被占用且工作队列都填满的情况下,你们系统的线程池对新进来的任务是怎么处理的?

2021-5-8 / 0评 / Java

/**
 * 1.corePoolSize:指定线程池中活跃的线程数量
 * 2.maximumPoolSize:指定线程池中最大线程数量
 * 3.keepAliveTime:超过corePoolSize个多余线程的存活时间
 * 4.unit:keepAliveTime的时间单位 TimeUnit
 * 5.workQueue:任务队列,被提交但尚未被执行的任务
 * 6.threadFactory:线程工厂,用于创建线程
 * 7.handler拒绝策略:当任务太多来不及处理时,如何拒绝任务
 */
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0){
                throw new IllegalArgumentException();  
        }

        if (workQueue == null || threadFactory == null || handler == null){
            throw new NullPointerException();
        }
        this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

该题考的是对常用线程池的拒绝策略的深度理解:
线程池的拒绝策略有四种: 
AbortPolicy(中止),
CallerRunsPolicy(调用者运行),
DiscardPolicy(中止不抛出异常),
DiscardOldestPolicy([删除队列中旧任务执行新任务]喜新厌旧)

上面四种策略根据业务选择不同的使用方式,比如我们业务支付系统使用的是CallerRunsPolicy策略
...
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
...

中止(Abort)策略是默认的饱和策略,该策略将会抛出未检查的RejectExecutionException异常,调用者可以捕获该异常,然后根据自己的需要编写处理业务逻辑代码。

抛弃(Discard)策略就是当新提交的任务无法保存到队列中等待执行时,"抛弃(Discard)"策略会悄悄抛弃该任务,不抛出任何异常。

抛弃最旧(DiscardOldest)策略将会抛弃下一个将被执行的任务,然后尝试重新提交新的任务,如果工作队列是一个优先队列,那么将抛弃最旧的策略将抛弃优先级最高的任务。
因此最好不要将"抛弃最旧的"饱和策略和优先级队列放在一起使用。

调用者运行(CallerRuns)策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量。
它不会在线程池的某个线程中执行新提交的任务,而是在一个调用了execute的线程中执行该任务。
比如我们常用的WebServer调用请求修改为使用有界队列和调用者运行的饱和策略,当线程池中的所有线程都被占用,并且工作队列被填满后,下一个任务会调用execute时在主线程中执行。
由于执行任务需要一定的时间,因此主线程至少在一段时间内不能提交任何任务,从而使得工作者线程有时间来处理完正在执行的任务。
在这期间,主线程不会调用 accept,因此到达的请求将被保存在网络TCP层的队列中而不是在应用程序的队列中。
如果请求流量还是持续不断的过来,这个时候回导致服务器过载,这种过载情况会逐渐向外蔓延开---从线程池到工作队列再到应用程序再到网络的TCP层,最终到达客户端,导致服务器在高负载下实现一种平缓的性能降低。从而保护线上服务。

public static void main(String[] args) {
    BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
    ThreadFactory factory = r -> new Thread(r, "caller-Runner线程执行");

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4,
            0L, TimeUnit.SECONDS, queue, factory,
            new ThreadPoolExecutor.CallerRunsPolicy());
    for (int i = 0; i < 1000; i++) {
        int n = i;
        threadPoolExecutor.submit(() -> {
            try {
                System.out.println("当前i=" + n + ", " + Thread.currentThread().getName() + "线程任务执行," + queue.size());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

本文共计 4541 字,感谢您的耐心浏览与评论。

声明:土豆丝不辣|版权所有,违者必究|如未注明,均为原创|转载请注明原文链接说明出处

0条回应:“问:如果线程池中所有的线程都被占用且工作队列都填满的情况下,你们系统的线程池对新进来的任务是怎么处理的?”