2

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

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();
  }

}
4

3 回答 3

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);
  }
}
于 2012-06-14T16:49:17.613 回答
1

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

您应该使用与线程不同的对象作为锁,因为 Thread 类执行它自己的同步。

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

关于检查 isInterrupted,如果 do 工作部分可能需要很长时间,可以在中间取消,并且不调用阻塞的方法并且会以任何方式抛出中断异常,则应该执行此操作。

于 2012-06-14T16:45:55.353 回答
0

使用等待/通知应该是一种更有效的方法。我也同意没有必要使用“易失性”的建议,并且在替代对象上同步以避免冲突是明智的。

其他一些建议:

  • 在别处启动线程,从静态块开始不是好的做法
  • 将执行逻辑放在“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();
  }

}
于 2012-06-14T16:58:39.310 回答