Anulowanie zadań
Najprościej anulowanie zadań można dokonać poprzez zmienną lokalną volatile np. finish. Gdzie zmienna będzie co pewien okres czasu sprawdzana i zadanie będzie można zakończyć.
public class CancellingUsingLocalVariable {
public static void main(String[] args) throws InterruptedException {
CancelledTask task = new CancelledTask();
Thread thread = new Thread(task);
thread.start();
TimeUnit.SECONDS.sleep(3);
task.cancel();
}
private static class CancelledTask implements Runnable {
private volatile boolean cancelled = false;
public void run() {
while (!cancelled) {
try {
doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private void doWork() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
}
public void cancel() {
this.cancelled = true;
}
}
}
Jednak jeśli wątek czeka na metodzie blokującej to musi się ona zakończyć zanim sprawdzony będzie ponownie status, to powoduje że nie jest to idealna metoda na anulowanie zadań.
Mechanizm wątków nie ma bezpośredniego mechanizmu aby zatrzymać inny wątek. Można jednak wykorzystać mechanizm przerwania - każdy wątek posiada zmienną interrupted którą można ustawić za pomocą metody interrupt(). Można sprawdzić za pomocą metody isInterrupted(). Statyczna i źle nazwana metoda statyczna interrupted() resetuje flagę i zwraca jej poprzednią wartość.
Większość metod blokujących co jakiś czas sprawdza flagę interrupted, czyści ją i wyrzuca wyjątek InterruptedException.
Mechanizm przerwania (interruption) jest najlepszą metodą anulowania zadań.
Samo ustawianie flagi nie jest jednoznaczne z przerwaniem wątku - to wątek ustala swoją politykę obsługi przerwań.
Należy pamiętać że działając bezpośrednio na zadaniu (Runnable, Callable) nie powinno się połykań błędy Interrupted exception a powinno się ustawić ponownie flagę interrrupted w aktualnym wątku:
Thread.currentThread().interrupt(). Należy pamiętać że jeśli używamy statycznej metody interrupted() i zwraca ona true jeśli nie rzucamy wyjątku powinniśmy chociać przywrócić daną flagę bo może jest ona obsługiwana przez wątek wykonujący zadanie. Należy pamiętać że to wątek ustala swoją politykę obsługi flagi interrupted!
public class CancellingUsingInterruption {
public static void main(String[] args) throws InterruptedException {
CancelledTask task = new CancelledTask();
Thread thread = new Thread(task);
thread.start();
TimeUnit.SECONDS.sleep(3);
thread.interrupt();
}
private static class CancelledTask implements Runnable {
public void run() {
while (Thread.getCurrentThread().isInterrupted()) {
try {
doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private void doWork() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
}
}
}
Najlepszą jednak metodą anulowania zadań jest korzystanie z obiektów zwracanych przez ExecutorService: Future. Reprezentują one rezultat zadania wykonywanego asynchronicznie.
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
Future<?> future = executor.submit(new CancelledTask());
TimeUnit.SECONDS.sleep(3);
future.cancel(true);
executor.shutdown();
executor.awaitTermination(1, TimeUnit.DAYS);
}
Jednak inną ważną zaletą interfejsu future jest to że przy pobieraniu rezultatów za pomocą metody get wszystkie błędy które zakończyły wykonywanie zadania są znów wyrzucane opakowane w ExecutionException.
Przykład wykonywania zadania przez 5 sekund po czym anulowanie zadania i pokazanie błędu jeśli wcześniej zakończył wykonywanie zadania:
public class CancellingAfterSomeTime {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(new CancelledTask());
try {
String result = future.get(5, TimeUnit.SECONDS);
System.out.println("Task returned: " + result);
} catch (ExecutionException e) {
System.out.println("Task throwed exception " + e.getCause().getMessage());
} catch (TimeoutException e) {
System.out.println("Timeout - cancelling task");
future.cancel(true);
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.DAYS);
}
private static class CancelledTask implements Callable<String> {
private Random r = new Random();
public String call() throws Exception {
while (!Thread.currentThread().isInterrupted()) {
try {
if (r.nextDouble() > 0.85) {
throw new RuntimeException("Exception from task");
}
if (r.nextDouble() > 0.75) {
// found result
break;
}
doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return "result";
}
private void doWork() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
}
}
}
.
Metody nieodpawiadające na przerwanie
Nie wszystkie metody blokujące sprawdzają falgę interrupted i wyrzucają wyjątek InterrruptedException np:
- czekanie na blokadzie na metodzie lock obiektu Lock lub na metodzie/bloku synchronized - w przypadku obiektu Lock można zamienić wywołanie metody na lockIterruptibly
- synchroniczne wywołania I/O z java.io - metody read i write w OutputStream i InputStream nie odpowiadają na przerwanie. Aby je zakończyć należy zamknąć powiązany obiekt Socket i metoda zostanie skończona - w takim wypadu należy przeciążyć metodę interrupt i zamknąć socket co spowoduje zakończenie metody read i write
Zatrzymywanie serwisów opartych na wątkach
Jeśli w aplikacji występuje serwis który sam zarządza swoimi wątkami i działa on nieustannie to przy zamykaniu aplikacji należy także zakończyć jego wątki ponieważ inaczej JVM nie zamknie się samoczynnie.
W tym wypadku należało by napisać mechanizm który będzie to robił - najlepiej wzorować się na takich właśnie serwisach - ExecutionService. Metody shutdown, awaitTermination, shutdownNow.
Brak komentarzy:
Prześlij komentarz