在上一个完成其工作之前启动下一个线程

时间:2014-11-21 19:49:28

标签: java multithreading scheduled-tasks

我的代码:

private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

public void beepForAnHour()
{
    scheduler.scheduleWithFixedDelay(new ExampleThread(), 1L, 1L, SECONDS);
}

private static class ExampleThread implements Runnable
{
    @Override
    public void run()
    {
        System.out.println("1:"+System.currentTimeMillis());

        try
        {
            Thread.sleep(5000L);
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }

        System.out.println("2:"+System.currentTimeMillis());
    }
}

结果示例如下:

1:1416599026988

2:1416599031988

1:1416599032989

2:1416599037989 

1:1416599038990

我不知道,代码中应该更改什么,所以下一个ExampleThread将在自上一个Thread开始后的1秒后开始,而不是从完成开始。

睡眠事件就是一个展示我问题的例子。

所以我希望看到的结果是:

1:1416599026988

1:1416599027988

1:1416599028988

1:1416599029988

1:1416599030988

2:1416599030988

1:1416599031988

2:1416599031988

1:1416599032988

2:1416599033988

2 个答案:

答案 0 :(得分:1)

ScheduledExecutorService无法做到这一点。 scheduleWithFixedDelay的文档说:

  

创建并执行一个周期性操作,该操作在给定的初始延迟后首先启用,随后在一次执行终止和下一次开始之间给定延迟。如果任务的任何执行遇到异常,则后续执行被禁止。否则,任务将仅通过取消或终止遗嘱执行人而终止。

(我的重点)

对于scheduleAtFixedRate,文档说:

  

创建并执行一个周期性操作,该操作在给定的初始延迟后首先启用,然后在给定的时间段内启用;执行将在initialDelay然后initialDelay+period,然后initialDelay + 2 * period之后开始,依此类推。如果任务的任何执行遇到异常,则后续执行被禁止。否则,任务将仅通过取消或终止执行者来终止。 如果此任务的执行时间超过其周期,则后续执行可能会延迟执行,但不会同时执行

此课程旨在阻止并发执行其任务。

但是,您可以将调度程序用于其唯一任务是运行ExampleThread的线程:

private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

public void beepForAnHour()
{
    scheduler.scheduleWithFixedDelay(new ThreadMaker(), 1L, 1L, TimeUnit.SECONDS);
}

private static class ThreadMaker implements Runnable
{
    @Override
    public void run() {
        Thread realThread = new Thread(new ExampleThread());
        realThread.start();
    }
}
private static class ExampleThread implements Runnable
{
    @Override
    public void run()
    {
        System.out.println("1:"+System.currentTimeMillis());

        try
        {
            Thread.sleep(5000L);
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }

        System.out.println("2:"+System.currentTimeMillis());
    }
}    

通过这种方式,您的计划任务仅运行创建新线程所需的一秒钟,因此调度程序将每秒运行一次(如果您使用scheduleAtFixedRate它将更精确)。而真正的"线程可以自由地并发运行。当然,如果您这样做,您将拥有多个ExampleThread对象,并且您还需要弄清楚如何与创建的线程进行通信以取消它们,因为它们是不能直接从ScheduledFuturescheduleWithFixedDelay返回的scheduleAtFixedRate对象中获取。

答案 1 :(得分:0)

ScheduledExecutorService的目的不是运行作业的并发实例,实际上下一个线程在当前完成之前没有启动。您使用的方法只是避免在当前作业结束时重新安排下一个作业的便捷方法。

您可以使用调度程序将下一个作业计划为运行的第一个命令。喜欢:

private static class ExampleThread implements Runnable
{
    @Override
    public void run()
    {
        scheduler.schedule(new ExampleThread(), 0L, 1L);
        System.out.println("1:"+System.currentTimeMillis());
        // ...
    }
}

然后你只启动第一个线程(使用新线程或使用ExecutorService)。 请务必将ScheduledExecutorService工厂中的corePoolSize参数增加到池中的其他线程。

我更喜欢的另一种方法是创建一个只启动你的工作的包装器。然后你安排你的包装:

private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private ExecutorService pool = Executors.newCachedThreadPool();

public void beepForAnHour() {
    scheduler.scheduleWithFixedDelay(new ExampleWrapperThread(), 1L, 1L, TimeUnit.SECONDS);
}

private class ExampleWrapperThread implements Runnable {
    @Override
    public void run() {
        pool.execute(new ExampleThread());
    }
}

private int _examplethread_id = 0;
private class ExampleThread implements Runnable {
    @Override
    public void run() {
        int sid = _examplethread_id++;
        System.out.println(sid + ":" + System.currentTimeMillis());
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(sid + ":" + System.currentTimeMillis());
    }
}