二十三. 并发实战-ScheduledThreadPoolExecutor使用

前言

本篇文章将对计划线程池ScheduledThreadPoolExecutor的使用进行说明。

正文

一. 延时执行有返回值的任务

延时执行的意思就是,向线程池提交任务时,会指定一个时间delay,线程池会延迟delay的时间再执行任务。通过ScheduledThreadPoolExecutorschedule()方法能够提交延时执行的任务,提交的任务只会执行一次。示例如下。

public class ScheduledThreadPoolExecutorTest {

    @Test
    public void ScheduledThreadPoolExecutor延时执行Callable() throws Exception {
        // 创建计划线程池
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor
                = new ScheduledThreadPoolExecutor(2);

        // 创建Callable对象
        Callable<Long> callable = new Callable<Long>() {
            final long currentTimeMillis = System.currentTimeMillis();

            @Override
            public Long call() throws Exception {
                return System.currentTimeMillis() - currentTimeMillis;
            }
        };

        // 延时2秒执行Callable
        // 这里的ScheduledFuture实际就是ScheduledFutureTask
        ScheduledFuture<Long> scheduledFuture = scheduledThreadPoolExecutor
                .schedule(callable, 2, TimeUnit.SECONDS);

        // 通过ScheduledFuture拿到执行结果
        Long result = scheduledFuture.get();
        System.out.println(result);
    }

}

运行测试程序,打印如下。

在这里插入图片描述

即任务执行的时间点与提交任务的时间点的间隔为2秒。同时由于提交的任务是Callable,所以可以通过ScheduledFuture来拿到延时执行任务的结果。

二. 延时执行无返回值的任务

通过ScheduledThreadPoolExecutorschedule()方法,也能延时执行一个无返回值的Runnable任务,不过通过ScheduledFuture只能拿到一个空返回值。示例如下。

public class ScheduledThreadPoolExecutorTest {

    @Test
    public void ScheduledThreadPoolExecutor延时执行Runnable() throws Exception {
        // 创建计划线程池
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor
                = new ScheduledThreadPoolExecutor(2);

        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            final long currentTimeMillis = System.currentTimeMillis();

            @Override
            public void run() {
                System.out.println(System.currentTimeMillis() - currentTimeMillis);
            }
        };

        // 延时2秒执行Runnable
        ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor
                .schedule(runnable, 2, TimeUnit.SECONDS);

        // 可以通过get()方法来阻塞主线程,直到任务执行完毕
        Object object = scheduledFuture.get();
        // Runnable任务无返回值,get()方法会拿到一个null
        Assert.assertNull(object);
    }

}

运行测试程序,打印如下。

在这里插入图片描述

三. 固定周期执行无返回值的任务

固定周期执行任务,就是指定一个时间,任务会以指定的时间来周期的触发执行。固定周期执行任务有两种情况:第一种是周期大于等于任务执行时间,第二种是周期小于任务执行时间

先看一下周期大于等于任务执行时间的示例,如下所示。

public class ScheduledThreadPoolExecutorTest {

    @Test
    public void ScheduledThreadPoolExecutor固定周期执行Runnable_周期大于任务执行时间() throws Exception {
        // 创建计划线程池
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor
                = new ScheduledThreadPoolExecutor(2);
        CountDownLatch countDownLatch = new CountDownLatch(10);

        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            final AtomicLong count = new AtomicLong(0L);
            // currentTimeMillis表示每次任务触发的时间点
            long currentTimeMillis = System.currentTimeMillis();

            @Override
            public void run() {
                long nowMillis = System.currentTimeMillis();
                // perid表示任务触发的时间间隔
                long perid = nowMillis - currentTimeMillis;
                currentTimeMillis = nowMillis;
                System.out.println(count.getAndIncrement() + "次,间隔毫秒数:" + perid);
                // 模拟任务执行需要花费1秒钟
                LockSupport.parkNanos(1000 * 1000 * 1000);
                if (count.get() >= 10) {
                    // 执行达到10次,关闭线程池
                    scheduledThreadPoolExecutor.shutdown();
                }
                countDownLatch.countDown();
            }
        };

        // 延时2秒,并以2秒作为固定周期来定时执行Runnable
        ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor
                .scheduleAtFixedRate(runnable, 2, 2, TimeUnit.SECONDS);

        countDownLatch.await();
    }

}

上述示例中,任务每次执行花费1秒,然后周期是2秒,运行测试程序,打印如下。

在这里插入图片描述

可以看到任务的触发周期是2秒,符合预期。

再看一下周期小于任务执行时间的示例,如下所示。

public class ScheduledThreadPoolExecutorTest {

    @Test
    public void ScheduledThreadPoolExecutor固定周期执行Runnable_周期小于任务执行时间() throws Exception {
        // 创建计划线程池
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor
                = new ScheduledThreadPoolExecutor(2);
        CountDownLatch countDownLatch = new CountDownLatch(10);

        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            final AtomicLong count = new AtomicLong(0L);
            // currentTimeMillis表示每次任务触发的时间点
            long currentTimeMillis = System.currentTimeMillis();

            @Override
            public void run() {
                long nowMillis = System.currentTimeMillis();
                // perid表示任务触发的时间间隔
                long perid = nowMillis - currentTimeMillis;
                currentTimeMillis = nowMillis;
                System.out.println(count.getAndIncrement() + "次,间隔毫秒数:" + perid);
                // 模拟任务执行需要花费3秒钟
                LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
                if (count.get() >= 10) {
                    // 执行达到10次,关闭线程池
                    scheduledThreadPoolExecutor.shutdown();
                }
                countDownLatch.countDown();
            }
        };

        // 延时3秒,并以2秒作为固定周期来定时执行Runnable
        ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor
                .scheduleAtFixedRate(runnable, 3, 2, TimeUnit.SECONDS);

        countDownLatch.await();
    }

}

上述示例中,任务每次执行花费3秒,然后周期是2秒,运行测试程序,打印如下。

在这里插入图片描述

可以看到任务的触发周期是3秒,与预期不符。

造成上述现象的原因就是:周期执行的任务,下一次的执行时间点是在上一次任务执行完毕后再设置,并且下一次的执行时间点是上一次设置的执行时间点加上周期。下面以一张示意图进行说明。

在这里插入图片描述

结合示意图,说明如下。

  • 周期大于等于任务执行时间。例如在1秒时,任务执行完毕,此时设置任务的下一次执行时间点为time = 0 + 2 = 2秒,那么在时间来到2秒时,又开始执行任务,在3秒时,任务执行完毕,此时设置任务的下一次执行时间点为time = 2 + 2 = 4秒,以此类推,触发任务执行的周期为2秒;
  • 周期小于任务执行时间。例如在3秒时,任务执行完毕,此时设置任务的下一次执行时间点为time = 0 + 2 = 2秒,由于这个时间点已经小于当前时间点,所以任务会立即又被执行,在6秒时,任务执行完毕,此时设置任务的下一次执行时间点为time = 2 + 2 = 4秒,以此类推,触发任务执行的周期为3秒。

四. 固定延时执行无返回值的任务

固定延时执行任务,就是指定一个延时时间delay,上一次任务执行完毕后,延时delay的时间,再执行下一次任务。示例如下。

public class ScheduledThreadPoolExecutorTest {

    @Test
    public void ScheduledThreadPoolExecutor固定延时执行Runnable() throws Exception {
        // 创建计划线程池
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor
                = new ScheduledThreadPoolExecutor(2);
        CountDownLatch countDownLatch = new CountDownLatch(10);

        // 创建Runnable对象
        Runnable runnable = new Runnable() {
            final AtomicLong count = new AtomicLong(0L);
            // currentTimeMillis表示每次任务触发的时间点
            long currentTimeMillis = System.currentTimeMillis();

            @Override
            public void run() {
                long nowMillis = System.currentTimeMillis();
                // perid表示任务触发的时间间隔
                long perid = nowMillis - currentTimeMillis;
                currentTimeMillis = nowMillis;
                System.out.println(count.getAndIncrement() + "次,间隔毫秒数:" + perid);
                // 模拟任务执行需要花费3秒钟
                LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
                if (count.get() >= 10) {
                    // 执行达到10次,关闭线程池
                    scheduledThreadPoolExecutor.shutdown();
                }
                countDownLatch.countDown();
            }
        };

        // 延时2秒,并以2秒作为固定延时来定时执行Runnable
        ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor
                .scheduleWithFixedDelay(runnable, 2, 2, TimeUnit.SECONDS);

        countDownLatch.await();
    }

}

运行测试程序,结果如下。

在这里插入图片描述

可以看到,触发任务执行的周期是5秒,这是因为任务执行会花费3秒,执行结束后,会设置下一次任务执行的时间点,下一次执行的时间点是任务上一次执行结束的时间点加上延时,所以任务执行的周期是5秒。下面是一个示意图。

在这里插入图片描述

总结

关于ScheduledThreadPoolExecutor的使用总结如下。

  1. ScheduledThreadPoolExecutor提供了schedule()方法,可以延时一定时间执行任务;
  2. ScheduledThreadPoolExecutor提供了scheduleAtFixedRate()方法,可以让任务周期的触发,但是如果任务的执行时间大于指定的周期,那么任务的触发周期会变成任务的执行时间;
  3. ScheduledThreadPoolExecutor提供了scheduleWithFixedDelay()方法,可以让任务执行完毕后,延时一定时间再进行下一次任务执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

樱花祭的约定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值