我们知道Java中的定时调度可以通过TimerTimerTask来实现。由于实现是单线程的,所以从JDK1.3发布以来,出现了几个问题,大致如下:
多个任务相互影响。 ScheduledExecutorService最初是为了解决TimerTimerTask的问题而设计的。它本质上是基于多线程机制,因此任务之间不会互相影响(只要线程数量足够;否则,某些任务会复用同一个线程)。
另外,内部使用的延迟队列本身是基于等待/唤醒机制实现的,因此CPU并不总是忙碌的。同时,通过多线程对CPU资源的复用也显着提升了性能。
如何使用
基本功能
ScheduledExecutorService继承自ExecutorService,因此它支持线程池的所有功能。提供了四种附加方法。我们来看看它的特点。
/** * 带延迟的调度,只执行一次* 调度后,可以使用Future.get() 阻塞,直到任务完成*/1. /** * 带延迟时间的调度,只执行一次。 * 调度完成后,可以使用Future.get()进行阻塞,直到任务执行完成并获取执行结果。 */2. 可调用,长延迟,以TimeUnit 为单位); /** * 带延迟时间的调度,定期执行,固定频率*/3. /** * ScheduledFuture 周期性执行,固定延迟*/4.
该方法用于带延迟时间的调度,仅执行一次。调度完成后,可以使用Future.get() 进行阻塞,直到任务完成。让我们看一个例子。
@Test public void test_schedule4Runnable() 抛出异常{ ScheduledExecutorService service=Executors.newSingleThreadScheduledExecutor(); ScheduledFuture future=service.schedule(() - { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace( ); } System.out.println('任务结束时间: ' + format(System.currentTimeMillis())) }, 1000, TimeUnit.MILLISECONDS); System.out.println('计划结束时间: ' + format(System .currentTimeMillis())); System.out.println('Runnable future's result is: ' + future.get() + ', and time is: ' + format(System.currentTimeMillis()));} 通过上述代码实现效果如下。延迟执行时间为1秒,任务执行3秒,任务只运行一次,并且使用Future.get()阻塞直到任务执行完成。
如下图所示,结果完全符合预期。
2 可安排通话
根据调度Runnable,我们将Runnable改为Callable看一下。
@Test public void test_schedule4Callable() 抛出异常{ ScheduledExecutorService service=Executors.newSingleThreadScheduledExecutor(); ScheduledFutureString future=service.schedule(() - { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace( ); } System.out.println('任务结束时间: ' + format(System.currentTimeMillis())); '成功' }, 1000, TimeUnit.MILLISECONDS) + format(System.currentTimeMillis()));得到的结果基本和Runnable一样。唯一的区别是future.get() 可以检索Callable 返回的实际结果。
3。固定费率表
该方法用于以固定频率运行任务循环。我们通过一个例子来看看它的效果。
@Test public void test_scheduleAtFixedRate() { ScheduledExecutorService service=Executors.newScheduledThreadPool(5); service.scheduleAtFixedRate(() - { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System .out.println('任务结束时间: ' + format(System.currentTimeMillis())) }, 1000L, 1000L, TimeUnit.MILLISECONDS); System.out.println('计划结束时间: ' + format(System.currentTimeMillis); ) () ))); while (true) { }} 在本例中,任务的初始延迟为1 秒,任务运行3 秒,任务运行之间的间隔为1 秒。我们来看看执行结果。
4。
此方法用于以固定延迟运行任务循环。我们通过一个例子看看它的效果。
@Test public void test_scheduleWithFixedDelay() { ScheduledExecutorService service=Executors.newScheduledThreadPool(5); service.scheduleWithFixedDelay(() - { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System .out.println('任务结束时间: ' + format(System.currentTimeMillis())) }, 1000L, 1000L, TimeUnit.MILLISECONDS); System.out.println('计划结束时间: ' + format(System.currentTimeMillis); ) () ))); while (true) { }} 在本例中,任务的初始延迟为1 秒,任务运行3 秒,任务运行之间的间隔为1 秒。我们来看看执行结果。
5。 ScheduleAtFixedRate 和ScheduleWithFixedDelay 之间的区别
这两种方法都是周期性执行任务,但是它们有什么区别呢?我们通过jdk文档找到了答案。
坦白来说,scheduleAtFixedRate()是固定频率,scheduleWithFixedDelay()是固定延迟。固定频率是相对于任务执行的开始时间而言的,固定延迟是相对于任务执行的结束时间而言的。这是最基本的区别。
从3和4的运行结果也可以看出这些差异。
阅读源码初体验
一般来说,源代码的入口点是构造方法。让我们来看看。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize);} public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());} 构造函数在术语中,它看起来像这样:信息:
ScheduledThreadPoolExecutor构造函数最终调用ThreadPoolExecutor构造函数,阻塞队列使用DelayedWorkQueue。上述信息中的第2点很重要,但由于篇幅限制,本文不会对其进行详细分析。
接下来我们看一下scheduleWithFixedDelay()方法。这个方法主要做了三件事。
验证输入参数,包括空指针和数值范围。将其包装在RunnableScheduledFuture 中以延迟其执行。 public ScheduledFuture? Range if (command==null || Unit==null) throw new NullPointerException(); throw new IllegalArgumentException() //2. 将Runnable 包装在`RunnableScheduledFuture` ScheduledFutureTaskVoid sft=new ScheduledFutureTaskVoid 中(command) , null,triggerTime(initialDelay,unit),unit.toNanos(-lay)); RunnableScheduledFutureVoid t=decorateTask(command, sft); //3. 延迟执行`RunnableScheduledFuture` Masu. deferredexecution()方法字面意思就是延迟执行。让我们仔细看看这个方法。
private void LateExecute(RunnableScheduledFuture? task) { //1. 检查线程池的执行状态if (isShutdown())reject(task); else { //2. 将任务添加到队列super.getQueue( ). add(task) ; //3. 如果任务添加到队列后线程池状态变为非执行, //该任务必须从队列中移除并且任务的`cancel()` 必须取消该任务通过Method if (isShutdown() !canRunInCurrentRunState(task.isPeriodic())Remove(task)) task.cancel(false); //4.任务加入队列后,如果线程池状态为Running,则该线程如果线程池状态健康,最终会调用ensurePrestart()方法来完成线程创建。主要有两个逻辑。
如果当前线程数未达到核心线程数,则创建核心线程。如果当前线程数达到核心线程数,则创建非核心线程,不放置任何任务。这与常规线程池不同。 ** * 与prestartCoreThread 相同,只是它确保即使corePoolSize 为0 也至少启动一个线程。 */void EnsurePrestart() { int wc=workerCountOf(ctl.get()) ); //1.当前线程数尚未达到如果核心线程数达到核心线程数,则移除核心线程. Create if (wc corePoolSize) addWorker(null, true); //2.如果当前线程数达到核心线程数,则创建非核心线程。 //2.1 任务不放入阻塞队列。这与(wc==0) 时的常规线程池不同。 addWorker(null, false);} 至此,除了DelayedWorkQueue延迟队列的源代码之外,我们已经分析了所有内容。
总结
首先,我了解了ScheduledExecutorService的基本功能,并在此基础上创建了一个演示demo,结果与基本功能完全相同。
经过初步分析内部实现原理和源码,我们发现队列的阻塞和线程的创建方式与普通线程池不同。
版权声明:本文由今日头条转载,如有侵犯您的版权,请联系本站编辑删除。