scheduledexecutorservice 只执行一次_ScheduledExecutorService原理分析

本文探讨ScheduledExecutorService的工作原理,通过代码示例展示了如何设置一个只执行一次的任务。分析源码发现,其内部使用延迟队列,并依赖BlockingQueue。scheduleAtFixedRate方法中的delayedExecute是关键,而任务执行状态由AtomicInteger标记。文章提出了关于任务未完成时CPU资源使用的问题,引发思考。

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

最近跟别人聊天,别人问我了一个问题ScheduledExecutorService的原理是啥呢?

说实话,一脸懵逼的就过去了,虽然知道这么个东西,说实话也没细看实现的原理,今天就来分析一下吧。

我们先从一段代码开始今天的旅程。

ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);

这里创建一个线程池为1的调度任务

//1秒后开始启动,每隔3s执行一次service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "我开始执行了"); }}, 1, 3, TimeUnit.SECONDS);

这里每隔3便会执行一次Runnable。

这便是调度任务服务的简单实用方法,下面来仔细的分析一下源码。

public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());}

进入源码可以看到这里的队列用的是延迟队列的实现。

static class DelayedWorkQueue extends AbstractQueue implements BlockingQueue {

通过跟踪源码分析可知,其实底层用的还是BlockingQueue(源码下篇分析),哈哈,看到这里是不是就很清楚了呢?

下面我们在来看scheduleAtFixedRate 这个方法,源码如下:

/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */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 sft = new ScheduledFutureTask(command, null, triggerTime(initialDelay, unit), unit.toNanos(period)); RunnableScheduledFuture t = decorateTask(command, sft); sft.outerTask = t; delayedExecute(t); return t;}

仅仅从代码分析来看,delayedExecute(t); 这个才是关键代码,其实方法的名称已经表明了,老外方法命名真规范,一眼就看出来要干啥。

两个判断条件我们就不说了,也没啥含量。

先来看sft这个对象的生成。先来分析一下参数的含义

第一个对象:Runnable对象,不用多说,

第二个对象:result,从Future可以看出,该任务执行可以携带返回值,不过这里传值是个null,可以不用关注

第三个对象:触发时间,从triggerTime可以看出是下次执行的时间

第四个对象:以纳秒计算的一个时间单位,也就是间隔时间。

sft对象生成之后,便开始了执行的过程。看到这里会有个疑问,任务到底是怎么执行的呢?

带着这个疑问,继续往下看delayedExecute这个方法。

private void delayedExecute(RunnableScheduledFuture> task) { if (isShutdown()) reject(task); else { super.getQueue().add(task); if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task)) task.cancel(false); else ensurePrestart(); }}

关键部分是ensurePrestart,那我们直接进入这个方法。

void ensurePrestart() { int wc = workerCountOf(ctl.get()); if (wc < corePoolSize) addWorker(null, true); else if (wc == 0) addWorker(null, false);}

继续进入关键代码addWorker

private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted;}

疑问:线程的状态是通过什么标记的呢?

81efbca3bbfff3999df7ec539cc13676.png

从897和898也基本能猜出来了,线程的状态是用AtomicInteger进行标记的。

fb9f51035a8dd6e8ef836bfd05ea95cd.png

其实这两个循环的目的只有一个,就是获取当前线程是否可执行。

关键代码如下:

if (compareAndIncrementWorkerCount(c)) break retry;

在这里可以看到,通过CAS计算之后,才会跳出该循环。

这里先放个疑问出来,如果前一次的任务还没有执行完,这里CPU会不会一直轮空?会不会造成cpu的飙升?

退出上面的循环后,实例化一个Worker 对象之后,进入了锁控制的部分,此处可以保证原子化。

if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true;}

从这里可以看出,增加一个worker对象的前提是线程的状态为:

rs < SHUTDOWN ||(rs == SHUTDOWN&& firstTask == null))

然后调用t.start();

此时该线程的任务开始执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值