创建一个在计时器上运行但可以随时唤醒的java线程

时间:2012-06-14 16:37:14

标签: java multithreading concurrency

我想创建一个定期运行某些东西(可运行)但可以在需要时唤醒的类。如果我可以封装整个事情,我想公开以下方法:

public class SomeService implements Runnable {


  public run() {
    // the code to run at every interval
  }

  public static void start() { }
  public static void wakeup() { }
  public static void shutdown() { }

}

不知怎的,我已经走到了这一步。但我不确定这是否是正确的方法。

public class SomeService implements Runnable {

  private static SomeService service;
  private static Thread thread;
  static {
    start();
  }

  private boolean running = true;

  private SomeService() {
  }

  public void run() {
    while (running) {
      try {
        // do what needs to be done
        // perhaps peeking at a blocking queue
        // or checking for records in a database
        // trying to be independent of the communication
        System.out.println("what needs to be done");
        // wait for 15 seconds or until notify
        synchronized (thread) {
          try {
            thread.wait(15000);
          } catch (InterruptedException e) {
            System.out.println("interrupted");
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private static void start() {
    System.out.println("start");
    service = new SomeService();
    thread = new Thread(service);
    thread.setDaemon(true);
    thread.start();
  }

  public static void wakeup() {
    synchronized (thread) {
      thread.notify();
    }
  }

  public static void shutdown() {
    synchronized (thread) {
      service.running = false;
      thread.interrupt();
      try {
        thread.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("shutdown");
  }

  public static void main(String[] args) throws IOException {

    SomeService.wakeup();
    System.in.read();
    SomeService.wakeup();
    System.in.read();
    SomeService.shutdown();

  }

}

我担心变量应该声明为volatile。并且还担心我应该检查thread.isInterrupted()的“需要做什么部分”。这看起来是正确的方法吗?我应该把它翻译成遗嘱执行人吗?如何强制在预定执行程序上运行?

修改

在尝试执行者之后,似乎这种方法似乎是合理的。你觉得怎么样?

public class SomeExecutorService implements Runnable {

  private static final SomeExecutorService runner 
    = new SomeExecutorService();

  private static final ScheduledExecutorService executor 
    = Executors.newSingleThreadScheduledExecutor();

  // properties

  ScheduledFuture<?> scheduled = null;

  // constructors

  private SomeExecutorService() {
  }

  // methods

  public void schedule(int seconds) {
    scheduled = executor.schedule(runner, seconds, TimeUnit.SECONDS);
  }

  public void force() {
    if (scheduled.cancel(false)) {
      schedule(0);
    }
  }

  public void run() {
    try {
      _logger.trace("doing what is needed");
    } catch (Exception e) {
      _logger.error("unexpected exception", e);
    } finally {
      schedule(DELAY_SECONDS);
    }
  }

  // static methods

  public static void initialize() {
    runner.schedule(0);
  }

  public static void wakeup() {
    runner.force();
  }

  public static void destroy() {
    executor.shutdownNow();
  }

}

3 个答案:

答案 0 :(得分:4)

对于初学者 - 你可能不想自己实现Runnable;你应该接受一个Runnable。如果您希望将类传递给其他人来执行,那么您应该只实现Runnable。

为什么不包装ScheduledExecutorService?这是一个快速(非常差,但应该是功能性的)实现。

public class PokeableService {

  private ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
  private final Runnable codeToRun;

  public PokeableService (Runnable toRun, long delay, long interval, TimeUnit units) {
    codeToRun = toRun;
    service.scheduleAtFixedRate(toRun, delay, interval, units);
  }

  public void poke () {
    service.execute(codeToRun);
  }
}

答案 1 :(得分:1)

变量不需要是易失性的,因为它们是在同步块中读取和修改的。

你应该使用一个不同的对象来锁定线程,因为Thread类会自己进行同步。

我建议使用单线程ScheduledExecutorService并删除sleep。然后,如果要在当前睡眠期间运行任务,可以再次将其提交给执行程序一次运行。只需在ExecutorService中使用ScheduledExecutorService扩展的执行或提交方法。

关于检查isInterrupted,你应该这样做,如果do work部分可能需要花费很多时间,可以在中间取消,并且不调用阻塞的方法,并且会以任何方式抛出中断的异常。

答案 2 :(得分:0)

使用wait / notify应该是一种更有效的方法。我也同意这样的建议:使用'volatile'不是必要的,并且在替代对象上进行同步将是明智的,以避免冲突。

其他一些建议:

  • 在其他地方启动线程,从静态块开始不是很好的做法
  • 将执行逻辑放在“execute()”方法或类似方法中是可取的

此代码实现了上述建议。另请注意,只有一个线程执行SomeService执行逻辑,并且它在上次完成之后将发生INTERVAL毫秒。手动触发的wakeUp()调用后,您不应该重复执行。

public class SomeService implements Runnable {

  private static final INTERVAL = 15 * 1000;
  private Object svcSynchronizer = new Object();
  private boolean running = true;

  private SomeService() {
  }

  public void run() {
    while (running) {
      try {
        // do what needs to be done
        // perhaps peeking at a blocking queue
        // or checking for records in a database
        // trying to be independent of the communication
        System.out.println("what needs to be done");

        // wait for 15 seconds or until notify
        try {
          svcSynchronizer.wait(INTERVAL);
        } catch (InterruptedException e) {
          // ignore interruptions
        }

      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }


  public void wakeUp() {
    svcSynchronizer.notifyAll();
  }

  public void shutdown() {
    running = false;
    svcSynchronizer.notifyAll();
  }

}