线程池队列满了之后任务丢弃,怎么解决

67 阅读2分钟

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

前言

最近有朋友去面了路客,面试官问的问题比较大,说实话很难细分。挑一道实战的来讲,线程池队列满了之后任务丢弃,怎么解决

开整

众所周知,线程池默认拒绝策略是丢弃并抛出异常,这样的话那溢出的任务就不见了,在实际业务中会受到影响。

优化:

  1. 改写拒绝策略,延迟任务重新投向线程池
  2. 打印对应任务参数,可以做塞回数据库,或者打印出来方便排查问题

代码

重新拒绝策略

static class RetryPolicy implements RejectedExecutionHandler {

        private DelayQueue<PullJobDelayTask> queue = new DelayQueue<>();

        private int i = 0;

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        	if(r instanceof MyThread){
                MyThread thread = (MyThread) r;
                log.info("异常线程参数:{}",thread);
            }
            queue.offer(new PullJobDelayTask(5, TimeUnit.SECONDS, r));
            log.error("等待5秒...");
            if(i>0){
                return;
            }
            CompletableFuture.runAsync(()-> {
                log.info("新增线程池...");
                while (true) {
                    try {
                        log.info("拉去任务...");
                        PullJobDelayTask task = queue.take();
                        executor.execute(task.runnable);
                    } catch (Exception e) {
                        log.error("抛出异常,{}", e);
                    }
                }
            });
            i++;
        }
    }

	@Data
    static class MyThread implements Runnable{

        int count;

        public MyThread(int count) {
            this.count = count;
        }

        @Override
        public void run() {
            log.info(String.valueOf(count));
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

实现了一个延迟任务,还有一个异步去拉取任务投放到线程池里头。

延迟任务

@Data
    static class PullJobDelayTask implements Delayed {

        private long scheduleTime;
        private Runnable runnable;

        public PullJobDelayTask(long scheduleTime, TimeUnit unit, Runnable runnable) {
            this.scheduleTime = System.currentTimeMillis() + (scheduleTime > 0 ? unit.toMillis(scheduleTime) : 0);
            this.runnable = runnable;
        }

        @Override
        public long getDelay(@NotNull TimeUnit unit) {
            return scheduleTime - System.currentTimeMillis();
        }

        @Override
        public int compareTo(@NotNull Delayed o) {
            return (int) (this.scheduleTime - ((PullJobDelayTask) o).scheduleTime);
        }
    }

验证

public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1),Executors.defaultThreadFactory(),new RetryPolicy());
        executor.execute(() -> {
            try {
                log.info("1");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        executor.execute(() -> {
            try {
                log.info("2");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        executor.execute(() -> {
            try {
                log.info("3");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        MyThread thread = new MyThread(4);
        executor.execute(thread);
    }

问题

Q:如何打印线程参数
A:RetryPolicy#rejectedExecution 里面通过判断 runnable 的类型,然后进行打印相关参数

Q: 有没有其他方案
A:有的,比如说有些就不用使用延迟队列,比如说我们是从数据库读取到的任务,执行成功就修改执行的标识,如果不成功或者任务被拒绝了,它下次扫描还是会继续塞回去

Q: 延迟队列如果宕机的话,任务也丢失了怎么办
A:这里的打印日志就很重要了,可以记录起来,或者加个 hook 回调钩子,在宕机的时候将这些数据写回数据库(kill -9 pid 不会调用 hook~)