2010-01-26 40 views
5

(tương tự như "Resettable Java Timer" nhưng có một số tinh tế tôi cần phải khám phá)timeout resettable trong Java

Tôi cần một tính năng timeout resettable, do đó nếu lớp học của tôi không thực hiện một hành động cụ thể trong một khoảng thời gian T0 (nơi T0 là trong khu phố của 50-1000msec), sau đó là một phương pháp được gọi là:

class MyClass { 
    static final private timeoutTime = 50; 
    final private SomeTimer timer = new SomeTimer(timeoutTime, 
     new Runnable() { public void run() { 
      onTimeout(); 
     }}); 

    private void onTimeout() { /* do something on timeout */ } 

    public void criticalMethod() { this.timer.reset(); } 
} 

tôi có thể sử dụng những gì để thực hiện điều này? Tôi quen thuộc với ScheduledExecutorService và ý tưởng gọi ScheduledFuture.cancel() và sau đó lên lịch lại nhiệm vụ có vẻ như như hoạt động, nhưng sau đó có nguy cơ tiềm ẩn nếu hủy() không thành công và tác vụ được lên lịch thực thi khi không được. Tôi cảm thấy như tôi đang thiếu một sự tinh tế ở đây.

Ngoài ra (có lẽ quan trọng hơn), có cách nào để kiểm tra việc triển khai của tôi/chứng minh rằng nó hoạt động đúng không?

chỉnh sửa: tôi đặc biệt quan tâm đến trường hợp criticalMethod() được gọi thường (có lẽ nhiều lần mỗi millisecond) ... nếu tôi sử dụng ScheduledExecutorService, nó chỉ có vẻ như một vấn đề tài nguyên tiềm năng để giữ cho việc tạo ra các nhiệm vụ theo lịch trình mới + hủy bỏ những cái cũ, thay vì có một cách trực tiếp để lên lịch lại một nhiệm vụ.

+0

Là phụ lục cuối cùng cho câu trả lời của tôi, bạn chắc chắn nên đọc qua Java Concurrency in Practice nếu bạn chưa có. Tất cả những ý tưởng trong câu trả lời của tôi đến từ cuốn sách đó (hoặc kinh nghiệm cá nhân, nhưng cuốn sách phù hợp với kinh nghiệm của tôi). –

Trả lời

3

Thuộc tính đã hủy được đính kèm với đối tượng nhiệm vụ. Do đó, tác vụ chưa bắt đầu khi bạn gọi cancel và tác vụ sẽ không chạy; hoặc nhiệm vụ đã bắt đầu khi bạn gọi cancel và tác vụ bị gián đoạn.

Cách xử lý sự gián đoạn tùy thuộc vào bạn. Bạn nên thường xuyên thăm dò ý kiến ​​Thread.interrupted() (mà, bằng cách này, đặt lại cờ bị gián đoạn, vì vậy hãy cẩn thận) nếu bạn không gọi bất kỳ chức năng gián đoạn nào (những tuyên bố InterruptedException trong mệnh đề throws của họ).

Tất nhiên, nếu bạn đang gọi các chức năng như vậy, bạn nên xử lý InterruptedException một cách hợp lý (bao gồm việc xác nhận lại cờ bị gián đoạn (Thread.currentThread().interrupt()) trước khi công việc của bạn trả về). :-)

Để trả lời chỉnh sửa của bạn, tạo đối tượng có giá rẻ, miễn là đối tượng của bạn không có nhiều trạng thái. Cá nhân tôi sẽ không lo lắng về nó quá nhiều trừ khi hồ sơ cho thấy nó là một nút cổ chai.

+0

thuộc tính bị hủy được gắn vào đối tượng nhiệm vụ .... nếu tôi lên lịch cùng một Runnable hai lần thì sao? sẽ không phải là thuộc tính bị hủy bỏ được giữ như là nhà nước với ScheduledFuture rằng kết quả từ lập kế hoạch nhiệm vụ? –

+0

Mỗi khi bạn lên lịch một 'Runnable', một' ScheduledFutureTask' mới được tạo cho nó. –

+0

ah, ok, điều đó có ý nghĩa. –

7

ok, đây là một nỗ lực khi sử dụng ScheduledExecutorService. Tôi rất ấn tượng với buổi biểu diễn; Tôi chạy chương trình thử nghiệm với các đối số 50 1 10 (thời gian chờ 50msec; mỗi 1msec ResettableTimer được đặt lại 10 lần) và nó hầu như không sử dụng CPU của tôi.

package com.example.test; 

import java.util.concurrent.ScheduledExecutorService; 
import java.util.concurrent.ScheduledFuture; 
import java.util.concurrent.ScheduledThreadPoolExecutor; 
import java.util.concurrent.TimeUnit; 
import java.util.concurrent.atomic.AtomicReference; 

public class ResettableTimer { 
    final private ScheduledExecutorService scheduler; 
    final private long timeout; 
    final private TimeUnit timeUnit; 
    final private Runnable task; 
    final private AtomicReference<ScheduledFuture<?>> ticket 
     = new AtomicReference<ScheduledFuture<?>>(); 
    /* use AtomicReference to manage concurrency 
    * in case reset() gets called from different threads 
    */ 

    public ResettableTimer(ScheduledExecutorService scheduler, 
      long timeout, TimeUnit timeUnit, Runnable task) 
    { 
     this.scheduler = scheduler; 
     this.timeout = timeout; 
     this.timeUnit = timeUnit; 
     this.task = task; 
    } 

    public ResettableTimer reset(boolean mayInterruptIfRunning) { 
     /* 
     * in with the new, out with the old; 
     * this may mean that more than 1 task is scheduled at once for a short time, 
     * but that's not a big deal and avoids some complexity in this code 
     */ 
     ScheduledFuture<?> newTicket = this.scheduler.schedule(
       this.task, this.timeout, this.timeUnit); 
     ScheduledFuture<?> oldTicket = this.ticket.getAndSet(newTicket); 
     if (oldTicket != null) 
     { 
      oldTicket.cancel(mayInterruptIfRunning); 
     } 
     return this; 
    } 


    static public void main(String[] args) 
    { 
     if (args.length >= 3) 
     { 
      int timeout = Integer.parseInt(args[0]); 
      int period = Integer.parseInt(args[1]); 
      final int nresetsPerPeriod = Integer.parseInt(args[2]); 
      ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1); 
      final ResettableTimer timer = new ResettableTimer(scheduler, 
        timeout, TimeUnit.MILLISECONDS, 
        new Runnable() { 
         public void run() { System.out.println("timeout!"); } 
        } 
      ); 

      // start a separate thread pool for resetting 
      new ScheduledThreadPoolExecutor(5).scheduleAtFixedRate(new Runnable() { 
       private int runCounter = 0; 
       public void run() { 
        for (int i = 0; i < nresetsPerPeriod; ++i) 
        { 
         timer.reset(false); 
        } 
        if ((++this.runCounter % 100) == 0) 
        { 
         System.out.println("runCounter: "+this.runCounter); 
        } 
       } 
      }, 0, period, TimeUnit.MILLISECONDS); 

      try 
      { 
       while (true) 
       { 
        Thread.sleep(1000); 
       } 
      } 
      catch (InterruptedException e) 
      { 
       System.out.println("interrupted!"); 
      } 
     } 
    } 
}