Java创建后台线程以定期执行某些操作

11

是否可以创建一个单独的后台线程来执行一些任务?我尝试了下面的程序,但它没有按我的预期工作。

public class Test {

    private static class UpdaterThread extends Thread {
        private final int TIMEOUT = 3000;

        public void run() {
            while (true) {
                try {
                    Thread.sleep(TIMEOUT);
                    System.out.println("3 seconds passed");
                } catch (InterruptedException ex) {
                }
            }
        }
    }

    /**
     * @param args
     *            the command line arguments
     */
    public static void main(String[] args) {
        try {
            Thread u = new UpdaterThread();
            u.start();
            while (true) {
                System.out.println("--");
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

我期望每3秒钟,“3秒已经过去”会在多个“--”字符串的流中打印出来。实际上,“3秒已经过去”从未被打印出来。为什么?如何创建一个独立于主线程的后台线程来执行某些操作?


不要扩展线程(很少需要),而是实现Runnable接口,详情请见这里 - David Kroukamp
1
当你捕获一个异常时,不采取任何措施通常不是一件好事。至少,你需要打印出异常的信息。否则你甚至都不知道是否捕获了异常。 - Arnab Datta
我同意Arnab Datta的说法,最好使用ex.printStackTrace();而不是留空catch块,这样比较无意义。 - David Kroukamp
你有多少个CPU内核? - Sergii Stotskyi
实际上,catch (InterruptedException e) 处理程序显然应该包含一个 break; -- 或者只需将 while 放在 try 块内。 - Marko Topolnik
5个回答

21
使用java.util.TimerTaskjava.util.Timer
Timer t = new Timer();

t.scheduleAtFixedRate(
    new TimerTask()
    {
        public void run()
        {
            System.out.println("3 seconds passed");
        }
    },
    0,      // run first occurrence immediately
    3000);  // run every three seconds

+1,对于OP来说,一些代码示例 - David Kroukamp
@Sergey,是的,它会。每个“Timer”对象都有一个后台线程。有关更多信息,请参阅javadoc。 - hmjd
@Sergey 是的,TimerTask 的优点在于它更好地表达了你的意图(即代码可读性),并且它已经实现了 cancel() 功能。 - David Kroukamp
..并且在计时器调用期间占用了线程池线程,如果计时器回调本身进行了阻塞调用...只是说-计时器任务并不总是最佳解决方案。 - Martin James
@hmjd 和其他人:对于我们不知道定时任务需要多长时间的情况,我想要通过确切的持续时间延迟执行。例如:定时器的第一次执行需要5秒,第二次需要3秒,第三次需要8秒等等。但是我希望在每次执行之前有一个确切的“休眠”时间。所以无论执行时间如何,我的延迟应该是相同的。我该如何实现这个? - java_enthu

7

代码会打印“3 seconds passed”。删除System.out.println("--"),你将更容易地看到它们;-)

现在您还可以使用ScheduledExecutorService,并使用Runnable而不是Thread

public class Test {

    private static class Updater implements Runnable {

        @Override
        public void run() {
            System.out.println("3 seconds passed");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Updater();
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(r, 0, 3, TimeUnit.SECONDS);

        Thread.sleep(10000);
        service.shutdown();

    }
}

这似乎是最好的答案 - I/O流被主线程中所有那些println()阻塞了。使用计时器或ScheduledExecutorService是可以的,但实际上涉及到运行额外的线程,并且在复杂的线程堆栈中的几个层次下实现起来很困难/笨拙 - 它会强制你进入一个状态机,而你可能不想要/需要。计时器等并不总是最好的答案! - Martin James
@MartinJames 它仅涉及运行一个额外的线程,这正是 OP 所要求的。当你说“它会强制你进入可能不需要/想要的状态机”时,我不确定我是否明白。 - assylias
1
好的,例子:您的线程调用函数A,函数A又调用函数B,函数B又调用函数C,在那里代码/数据指示需要等待2秒钟才能继续操作并返回。对于OP代码,计时器/执行器是可以的 - 我只是想说,计时器/执行器并不适用于所有需要等待的情况。 - Martin James

3
您可以使用上述方法定期运行代码,虽然 TimerTask 可能更简单。

关于您的输出,我怀疑您的主线程没有让您的 UpdaterThread 运行,因为它在一个非常紧密的循环中。请注意,这将取决于可用的CPU /核心等。

您是否考虑在 main 线程中睡眠,或使用 Thread.yield()?请注意该链接页面中的规定:

何时使用yield()?

我会说几乎从不使用。其行为未经标准定义,并且通常有更好的方法来执行您可能需要使用yield()执行的任务:如果您要仅使用CPU的一部分,则可以通过估计线程在其最后一批处理中使用了多少CPU,然后睡眠一段时间进行补偿:请参见sleep()方法;

还要注意此有趣的文章,介绍如何处理线程中断。


2

有很多答案,但没有人解释为什么他的例子不起作用。 System.out 是输出流,因此在开始向此流写入后,JAVA会锁定它,所有其他线程都将等待锁定应用于流。在流解锁之后,另一个线程将能够使用此流。

要使您的示例起作用,您应该在主线程的while循环中添加Thread.sleep


1
我建议使用ScheduledExecutorService。要每3秒运行您的UpdaterThread(),可以这样做:
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new UpdaterThread(), 0, 3000, TimeUnit.MILLISECONDS);

您可以在这里阅读更多内容:Java教程 - 执行器接口


不是我干的,但我怀疑那个踩票者认为将这样的任务排队到线程池中(或使用计时器)可能过度设计,导致过多和不必要的上下文切换? - Martin James
@MartinJames:有可能。由于@assylias的答案也被踩了,所以他/她显然对在这里使用ScheduledExecutorService持反对意见。 - Keppil
那么,投反对票的人呢?我只会在回答完全错误的情况下才会投反对票。如果我认为有更好的解决方案或更好的解释,我会添加评论。 - Martin James

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接