Java - 主方法中的Thread.sleep

时间:2011-12-27 23:23:57

标签: java concurrency thread-safety

我正在审查Java代码,它本质上是一个定期移动/读取/解析某些文件并将数据输出到数据库的重复过程。重复部分(大致)完成如下:

public class CollectorMain {
    public static boolean signalRecieved = false;
    public static void main(String[] args) {
         Runtime.getRuntime().addShutdownHook(new Thread() {
              public void run() {  
              shutdown(); 
         }});
         while(!signalRecieved) {
              Collector.execute();
              try {
                  Thread.sleep(60 * 1000);
              } catch (InterruptedException e) {
                  break;
              }
         }
         // some shutdown logic
    }
    public static void shutdown() {
        signalReceived = true;
    }
}

public class Collector() {
    public static void execute() {
        // Move files from the queue dir to temp location
        // Read, parse files and insert records into database. 
        // Then delete the processed files
    }
}

我的建议是将代码重构为

  1. 创建Collector实例并将静态execute()方法重构为实例方法
  2. 使用Runnable或TimerTask来处理调用
  3. 我的论点是,从main方法使用Thread.wait并将其与静态访问相结合并不是处理可重复进程的好方法,尤其是执行文件IO。作者回复(引用)

      

    Runnable的描述说“应该由任何类实现   其实例旨在由一个线程执行“。事实上,我   由于成本原因,我故意避免在这个程序中使用线程   vrs.performance要求。

    以下是同一讨论的另一个引用,希望有助于澄清作者的立场

      

    从技术上讲,Java根本不执行,它由JVM解释   然后执行机器指令,模拟Java   代码正在执行。所以它真的是在a上执行的JVM   线程或多个线程。

         

    但作为Java代码编写者,我并不在意。如果我不创建“线程”   在Java中,那么JVM的工作就好像没有了   线程 - 即使JVM使用线程“隐藏”。

         

    未执行Java Pause,它由一系列机器模拟   可能会或可能不会调用操作系统“等待”的指令。 (它可能会,   因为JVM不想旋转,燃烧CPU周期,但那就是   JVM实现选择)。

    所以我有两个问题:

    1. 在此实例中,将Thread.wait置于主要方法中是否是合法,安全且可取的可重复任务方式?如果没有,为什么不,因为只有一个(主要)执行线程?
    2. 在这种情况下使用静态方法有什么问题?(如果有的话)?
    3. 如果您有任何其他问题,我很乐意提供其他信息。

4 个答案:

答案 0 :(得分:6)

你真的在争论设计决策,而不是绩效决策。

就我所见,你的同事关于如何实现Thread.sleep的声明基本上是不正确的。在合理的操作系统上的合理JVM上,Thread.sleep()是使用O / S本机方法实现的,用于停止线程(或将其置于“定时等待”状态或您想在操作系统上调用它的任何内容) 。换句话说,当线程处于休眠状态时,它会占用零CPU。在任何情况下,TimerTask都将使用Thread.sleep(或类似的 - 我不只是回忆它是否使用Java 5中引入的park()方法,但它基本没有区别)。

JVM通常不会做出关于线程的秘密决策。如果你要求另一个线程,你会得到一个;如果你不这样做,你就不会。将为垃圾收集等创建一些“管家”线程,但就您的代码而言,您可以假设没有秘密聪明的线程创建。

所以,回到你的问题:

  • 在主线上睡觉本身是完全安全的;显然,当主线程处于休眠状态时,它不会执行任何操作,但如果你不需要它,那就没关系了;
  • 是否直接在代码中使用Thread.sleep()或使用其中一个“计时器”实用程序方法再次是一个设计决策,取决于您是否要使用从代码中删除实现细节的通用库,或者是否您希望将这些细节呈现在您的控制之下;在性能方面,如果你正确地实现它,它将几乎没有可能;
  • 你是否有一个静态方法或者有一个Collector实例并不重要 - 这只是一个你需要根据看起来更直观的设计决策:你认为Collector是一个“通用实用程序”类,或作为您要求执行操作的状态的“事物”?

答案 1 :(得分:4)

你让sleepwait感到困惑。 sleep使当前线程空闲一段时间,然后线程自行重启。 wait是Object的一种方法。它用于将线程置于等待状态,并且如果另一个线程在同一对象上使用notifynotifyAll唤醒它,它将只会退出此状态。如果只执行一个线程,则使用wait将使程序永久挂起。

阅读http://docs.oracle.com/javase/tutorial/essential/concurrency/index.html

答案 2 :(得分:1)

我认为您的同事并不真正了解JVM及其线程模型;你的代码不会神奇地多线程,除非你明确地这样做。此外,我认为你们两个都在努力工作。尝试使用Quartz库:http://quartz-scheduler.org/documentation/quartz-2.1.x/quick-start

我的理由是Java Threading很难做到,特别是当你发现自己正在进行等待/通知时,并且正在努力解决所有边缘情况。石英库将这一切都抽象出来,并将重复的方面置于熟悉的CRON模式之后。

我会完全避免使用TimerTask,因为如果在你的运行过程中发生任何未处理的异常,它会有一种悄悄失败的恶习。

无论您是否使用解决方案,调用静态方法都没有什么大不了的,关键是要了解哪些状态在线程之间共享,要么消除共享,要么同步对它的访问,以便所有线程获得一致的数据视图。如果我在你的位置,我会给Quartz一个机会。如果您不愿意添加另一个库,JDK 1.5(我认为)引入了ScheduledExectutorService,我认为它会重复执行任务。

无论你走哪条路,都不要自己编写调度/执行框架。这是Quartz或ScheduledExectutorService中已解决的问题。

答案 3 :(得分:0)

在发布的代码示例中,我会使用Object.wait(60 * 1000)代替Thread.sleep(60 * 1000),然后在shutdown方法中,在设置notifyAll()后添加对signalReceived = true的调用}。这将需要在notify和wait方法周围添加必要的同步块。至少,这将提供“等待循环”能够立即退出的好处,而不是等待超时首先经过。 (请参阅JB Nizet关于此的一些其他细节的答案。)

从整体的角度来看 - 我有收集器实现TimerTask({3}}的子接口),使用Runnable安排它,并完成它。

有些用户会出于性能原因提倡使用静态,我认为这主要是历史性的。将所有内容保持为非静态将允许将来在同一JVM中使用多个实例,并且能够使用子类来覆盖方法并自定义基本实现。