2017-07-29 2 views
3

Wenn ein Befehl mit fester Rate an einem beliebigen ScheduledExecutorService geplant wird, wird ScheduledFuture zurückgegeben, die ebenfalls abgebrochen werden kann. Aber "Abbrechen" bietet keine Garantie dafür, dass der Befehl nach Abbruch der Rückgabe nicht ausgeführt wird, z. B. weil der Befehl bereits in der Mitte der Ausführung war, als "cancell" aufgerufen wurde.So stornieren Sie ShceduledFuture und warten Sie, bis Runnable beendet wird, wenn zum Zeitpunkt der Stornierung Runnable ausgeführt wird?

Für die meisten Anwendungsfälle reicht die Funktionalität aus. Aber ich habe mich mit Usecase beschäftigt, wenn der aktuelle Thread nach dem Abbrechen blockiert werden muss, wenn der Befehl bereits ausgeführt wird, und warten bis der Befehl erledigt ist. Mit anderen Worten, Thread, der cancel aufgerufen hat, sollte nicht vorwärts gehen, wenn der Befehl noch ausgeführt wird. Abbrechen mit mayInterruptIfRunning = true ist auch nicht geeignet, da ich die aktuellen Ausführungen nicht unterbrechen will, muss ich nur auf normales abschließen warten.

Ich habe nicht gefunden, wie man diese Anforderungen über Standard-JDK-Klassen erreicht. Frage1: War ich falsch und diese Art von Funktionalität existiert?

Also beschloss ich, es selbst zu implementieren: import java.util.concurrent. *;

public class GracefullyStoppingScheduledFutureDecorator implements ScheduledFuture { 

/** 
* @return the scheduled future with method special implementation of "cancel" method, 
* which in additional to standard implementation, 
* provides strongly guarantee that command is not in the middle of progress when "cancel" returns 
*/ 
public static ScheduledFuture schedule(Runnable command, long initialDelay, long period, TimeUnit unit, ScheduledExecutorService scheduler) { 
    CancellableCommand cancellableCommand = new CancellableCommand(command); 
    ScheduledFuture future = scheduler.scheduleAtFixedRate(cancellableCommand, initialDelay, period, unit); 
    return new GracefullyStoppingScheduledFutureDecorator(future, cancellableCommand); 
} 

private GracefullyStoppingScheduledFutureDecorator(ScheduledFuture targetFuture, CancellableCommand command) { 
    this.targetFuture = targetFuture; 
    this.runnable = command; 
} 

private final ScheduledFuture targetFuture; 
private final CancellableCommand runnable; 

@Override 
public boolean cancel(boolean mayInterruptIfRunning) { 
    runnable.cancel(); 
    return targetFuture.cancel(mayInterruptIfRunning); 
} 

@Override 
public long getDelay(TimeUnit unit) { 
    return targetFuture.getDelay(unit); 
} 

@Override 
public int compareTo(Delayed o) { 
    return targetFuture.compareTo(o); 
} 

@Override 
public boolean isCancelled() { 
    return targetFuture.isCancelled(); 
} 

@Override 
public boolean isDone() { 
    return targetFuture.isDone(); 
} 

@Override 
public Object get() throws InterruptedException, ExecutionException { 
    return targetFuture.get(); 
} 

@Override 
public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 
    return targetFuture.get(timeout, unit); 
} 

private static class CancellableCommand implements Runnable { 

    private final Object monitor = new Object(); 
    private final Runnable target; 
    private boolean cancelled = false; 

    private CancellableCommand(Runnable target) { 
     this.target = target; 
    } 

     public void cancel() { 
      synchronized (monitor) { 
       cancelled = true; 
      } 
     } 

     @Override 
     public void run() { 
      synchronized (monitor) { 
       if (!cancelled) { 
        target.run(); 
       } 
      } 
     } 

    } 

} 

Question2: jeder Fehler im Code oben finden kann?

Antwort

2

Frage2: Könnte jemand Fehler im obigen Code finden?

Es ist hypothetisch Deadlock, die durch folgende Szenario wiedergegeben werden kann:

  1. Having T1 Threads, M1 Monitor hält
  2. Spiel-Task (hält den Monitor M2) auf Thread T2 ausgeführt wird und will Geben Sie M1 ein, so dass T2 warten muss, bis T1 den Monitor M1 verlässt.
  3. T1 entschied sich, die Aufgabe abzubrechen, aber weil der Monitor M2 durch die Task selbst gesperrt ist, haben wir den Deadlock.

wahrscheinlichste Szenario abovr unwirklich ist, sondern von allen möglichen Fällen zu schützen, entschied ich Code in Schloss frei neu zu schreiben:

public class GracefullyStoppingScheduledFuture { 

/** 
* @return the scheduled future with method special implementation of "cancel" method, 
* which in additional to standard implementation, 
* provides strongly guarantee that command is not in the middle of progress when "cancel" returns 
*/ 
public static GracefullyStoppingScheduledFuture cheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit, ScheduledExecutorService scheduler) { 
    CancellableCommand cancellableCommand = new CancellableCommand(command); 
    ScheduledFuture future = scheduler.scheduleAtFixedRate(cancellableCommand, initialDelay, period, unit); 
    return new GracefullyStoppingScheduledFuture(future, cancellableCommand); 
} 

private GracefullyStoppingScheduledFuture(ScheduledFuture targetFuture, CancellableCommand command) { 
    this.targetFuture = targetFuture; 
    this.runnable = command; 
} 

private final ScheduledFuture targetFuture; 
private final CancellableCommand runnable; 

public void cancelAndBeSureOfTermination(boolean mayInterruptIfRunning) throws InterruptedException, ExecutionException { 
    try { 
     targetFuture.cancel(mayInterruptIfRunning); 
    } finally { 
     runnable.cancel(); 
    } 
} 

private static class CancellableCommand implements Runnable { 

    private static final int NOT_EXECUTING = 0; 
    private static final int IN_PROGRESS = 1; 
    private static final int CANCELLED_WITHOUT_OBSTRUCTION = 2; 
    private static final int CANCELLED_IN_MIDDLE_OF_PROGRESS = 3; 

    private final AtomicInteger state = new AtomicInteger(NOT_EXECUTING); 
    private final AtomicReference<Thread> executionThread = new AtomicReference<>(); 
    private final CompletableFuture<Void> cancellationFuture = new CompletableFuture<>(); 
    private final Runnable target; 

    private CancellableCommand(Runnable target) { 
     this.target = target; 
    } 

    public void cancel() throws ExecutionException, InterruptedException { 
     if (executionThread.get() == Thread.currentThread()) { 
      // cancel method was called from target by itself 
      state.set(CANCELLED_IN_MIDDLE_OF_PROGRESS); 
      return; 
     } 
     while (true) { 
      if (state.get() == CANCELLED_WITHOUT_OBSTRUCTION) { 
       return; 
      } 
      if (state.get() == CANCELLED_IN_MIDDLE_OF_PROGRESS) { 
       cancellationFuture.get(); 
       return; 
      } 
      if (state.compareAndSet(NOT_EXECUTING, CANCELLED_WITHOUT_OBSTRUCTION)) { 
       return; 
      } 
      if (state.compareAndSet(IN_PROGRESS, CANCELLED_IN_MIDDLE_OF_PROGRESS)) { 
       cancellationFuture.get(); 
       return; 
      } 
     } 
    } 

    @Override 
    public void run() { 
     if (!state.compareAndSet(NOT_EXECUTING, IN_PROGRESS)) { 
      notifyWaiters(); 
      return; 
     } 

     try { 
      executionThread.set(Thread.currentThread()); 
      target.run(); 
     } finally { 
      executionThread.set(null); 
      if (!state.compareAndSet(IN_PROGRESS, NOT_EXECUTING)) { 
       notifyWaiters(); 
      } 
     } 
    } 

    private void notifyWaiters() { 
     if (state.get() == CANCELLED_WITHOUT_OBSTRUCTION) { 
      // no need to notify anything 
      return; 
     } 
     // someone waits for cancelling 
     cancellationFuture.complete(null); 
     return; 
    } 

} 
Verwandte Themen