ScheduledThreadPoolExecutor分析与线程池防坑

Java线程池使用不当会导致很多线上问题,常见的有:
  • 线程池默认使用无界队列,任务过多时,JVM OOM
  • 不设置最大线程数,线程数目暴涨
  • 定时执行,执行一次任务耗时太长甚至一直阻塞,达不到定时执行的目的
  • 线程池内的线程是非守护线程,停止JVM时出问题
  • 使用submit方法,没有调用future.get()导致异常被吞,execute不会有异常被吞的问题
  • 死锁问题,并不常见,但是有时候写复杂了可能会出现。一句话,使用线程池和诸如阻塞队列这种抽象层次比较高的工具时,尽量不要再用低层次的类,如lock,wait(),notify()等,这样混用会导致难以排查的问题。
ScheduledExecutorService分析
	ScheduledExecutorService scheduledService = Executors.newScheduledThreadPool(4);
        scheduledService.scheduleAtFixedRate(() -> {
            try {
                System.out.println("pool thread:" + Thread.currentThread().isDaemon());
                System.out.println("start at:" + new Date());
                Thread.sleep(10000);
                System.out.println("end at:" + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 5, TimeUnit.SECONDS);

上面的代码块意图每5秒执行一次任务,但是执行这个任务需要耗费10秒(sleep),那么肯定不能达到5秒一次的效果。分析一下原因。
定时执行功能的实现类是 ScheduledThreadPoolExecutor ,它是一个线程池加延迟队列来实现的,延迟队列是使用堆来实现的,也就是根元素值最大或者值最小,在定时任务执行这个场景下,根元素就是下一个要执行的任务,然后有一个等待时间。

	public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

然后看 ScheduledFutureTask 这个类的 run() 方法

	public void run() {
            boolean periodic = isPeriodic();//是否周期执行
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();//不是执行一次就完了
            else if (ScheduledFutureTask.super.runAndReset()) {//执行
                setNextRunTime();//设置下次时间
                reExecutePeriodic(outerTask);//加入队列
            }
        }
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(true)) {
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();//决定线程池是否新增线程,未达到核心线程数,有新任务则加线程
        }
    }

可以看到如果一次执行时间很长,是达不到定时执行的目的,最终的表现就是一次连着一次执行,如果我们确实需要精准的定期执行,不关注每次多长时间,该怎么办?很简单,再弄一个线程池专门用来干活

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值