java 任务之间具有相对延迟的ScheduledExecutorService

2eafrhcq  于 2023-01-24  发布在  Java
关注(0)|答案(2)|浏览(139)

我正在尝试创建一个ScheduledExecutorService,其中一次只有一个任务处于活动状态,并且只有在一个任务完成后,下一个任务才会以任意延迟量开始延迟。
作为一个非常简单的例子,看看这个方法。这个方法的想法是调度10个Runnable来模拟从10到1的倒计时。每个间隔需要一秒(想象一下这是一个任意的秒数,但是,我不能在我的用例中使用scheduleAtFixedRate)。

private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

public void startCountdown() {
  for (int i = 10; i > 0; i--) {
    int countdownNumber = i;
    scheduler.schedule(() -> {
      System.out.println(countdownNumber);
    }, 1, TimeUnit.SECONDS);
  }
}

然而,这将只是一次打印所有10个数字,而不是在每个值之间等待一秒钟。我可以绕过这一点的唯一方法(据我所知)是计算绝对延迟,而不是相对延迟。
虽然可以计算每个项目的绝对时间,但这会相当麻烦。Java中是否有某种结构允许我一次对多个项目进行排队,但在每个项目之间的**等待延迟完成,而不是一次处理每个延迟?

ffscu2ro

ffscu2ro1#

TL;医生

  • 不要使用倒计时数字直接安排任务。一个数字用于安排等待的秒数(1,2,3,...),另一个数字用于倒计时(9,8,7,...)。
  • 使用scheduleAtFixedRate将任务调度为越来越多的秒数。执行器服务不需要是单线程的。

详情

重新计划自身的任务

Java中是否有某种结构允许我一次对多个项目进行排队,但在每个项目之间等待延迟结束,而不是一次处理每个延迟?
如果在开始调度时有一段未知的时间,那么一次只能运行一个任务,让任务自己重新调度。
要使任务能够重新调度自身,请将对ScheduledExecutorService的引用传递给任务对象(您的RunnableCallable)作为构造函数中的参数。任务完成其主要工作后,它会发现/计算下一次运行所需的时间。然后,任务提交自己(this),以及在下一任务执行之前所经过的时间量。
我已经在Stack Overflow上发布了答案,其中包含了重新调度自己的任务的代码。我希望其他人也有。搜索以了解更多。
关于你的问题的"倒计时"方面,请继续阅读。

倒数

您使用计划执行器服务的方法是正确的,问题是您在该类上调用了错误的方法。
调用schedule意味着你安排了几个任务在一秒钟后运行,所有这些任务都从你调用schedule的那一刻开始,所以每个任务都在你调用schedule的一秒钟后运行,所以这10个任务都在几乎同一时刻等待一秒钟:十个瞬间,每一个瞬间,每一个瞬间就是for循环继续所需的时间。

一米八一米

您要查找的方法是scheduleAtFixedRate。引用文档:
提交一个周期性操作,该操作在给定的初始延迟之后首先变为启用状态,然后在给定的周期内变为启用状态;即,执行将在初始延迟之后开始,然后是初始延迟+周期,然后是初始延迟+2 * 周期,等等。

private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

public void countdown( ScheduledExecutorService scheduler ) 
{
    for ( int i = 1 ; i <= 10 ; i++ ) 
    {
        int countdownNumber =  10 - i ;  // For 9 through 0. Add 1 for 10 through 1. 
        scheduler.scheduleAtFixedRate 
        (
            () -> { System.out.println( countdownNumber ) ; } , 
            i ,  // 1 second, then 2 seconds, then 3 seconds, and so on to 10 seconds.
            TimeUnit.SECONDS 
        ) ;
    }
}

… Eventually shut down your scheduled executor service.

请注意,这种方法并不要求ScheduledExecutorService是单线程的。

完整示例

下面是一个完整的示例应用程序。

package work.basil.example.countdown;

import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Countdown
{
    public static void main ( String[] args )
    {
        Countdown app = new Countdown();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "INFO - Demo start. " + Instant.now() );

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();  // Our code does *not* require the executor service to be single-threaded. But for this particular example, we might as well do it that way.
        this.countdown( scheduler );
        this.shutdownAndAwaitTermination( scheduler , Duration.ofMinutes( 1 ) , Duration.ofMinutes( 1 ) );

        System.out.println( "INFO - Demo end. " + Instant.now() );
    }

    public void countdown ( final ScheduledExecutorService scheduler )
    {
        Objects.requireNonNull( scheduler ) ; 

        for ( int i = 1 ; i <= 10 ; i++ )
        {
            int countdownNumber = 10 - i;  // For 9 through 0. Add 1 for 10 through 1.
            scheduler.scheduleAtFixedRate
            (
                    ( ) -> { System.out.println( "Countdown: " + countdownNumber + " at " + Instant.now() ); } ,
                    i ,  // 1 second, then 2 seconds, then 3 seconds, and so on to 10 seconds.
                    TimeUnit.SECONDS
            );
        }
    }

    // My slightly modified version of boilerplate code taken from Javadoc of `ExecutorService`.
    // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ExecutorService.html
    void shutdownAndAwaitTermination ( final ExecutorService executorService , final Duration waitForWork , final Duration waitForRemainingTasks )
    {
        Objects.requireNonNull( executorService ) ;
        Objects.requireNonNull( waitForWork ) ;
        Objects.requireNonNull( waitForRemainingTasks ) ;

        executorService.shutdown(); // Disable new tasks from being submitted
        try
        {
            // Wait a while for existing tasks to terminate
            if ( ! executorService.awaitTermination( waitForWork.toMillis() , TimeUnit.MILLISECONDS ) )
            {
                executorService.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if ( ! executorService.awaitTermination( waitForRemainingTasks.toMillis() , TimeUnit.MILLISECONDS ) )
                { System.err.println( "ExecutorService did not terminate." ); }
            }
        }
        catch ( InterruptedException ex )
        {
            // (Re-)Cancel if current thread also interrupted
            executorService.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
        System.out.println( "DEBUG - shutdownAndAwaitTermination ran. " + Instant.now() );
    }
}

运行时:

INFO - Demo start. 2023-01-20T21:24:47.379244Z
Countdown: 9 at 2023-01-20T21:24:48.390269Z
Countdown: 8 at 2023-01-20T21:24:49.390045Z
Countdown: 7 at 2023-01-20T21:24:50.389957Z
Countdown: 6 at 2023-01-20T21:24:51.386468Z
Countdown: 5 at 2023-01-20T21:24:52.390168Z
Countdown: 4 at 2023-01-20T21:24:53.386538Z
Countdown: 3 at 2023-01-20T21:24:54.387583Z
Countdown: 2 at 2023-01-20T21:24:55.386705Z
Countdown: 1 at 2023-01-20T21:24:56.389490Z
Countdown: 0 at 2023-01-20T21:24:57.387566Z
DEBUG - shutdownAndAwaitTermination ran. 2023-01-20T21:24:57.391224Z
INFO - Demo end. 2023-01-20T21:24:57.391966Z

顺便说一句,由于各种原因,计划任务并不总是准时启动。
另外,要注意跨线程发送到System.out的消息并不总是按时间顺序出现在控制台上,如果你关心顺序,总是包括并研究一个时间戳,比如Instant#now

mec1mxoz

mec1mxoz2#

您可以在任务中安排下一个呼叫。

void countdown(final int i) {
    scheduler.schedule(() -> {
        System.out.println(i);
        if (i > 0) countdown(i - 1);
    }, 1, TimeUnit.SECONDS);
}
// ...
countdown(10);

相关问题