Dlaczego możesz potrzebować usługi ExecutorService dla 1 wątku?
Za pomocą metody Executors.newSingleThreadExecutor można utworzyć obiekt ExecutorService z pulą zawierającą pojedynczy wątek. Logika takiej puli jest następująca:
- Usługa wykonuje tylko jedno zadanie naraz.
- Jeśli wyślemy N zadań do wykonania, wszystkie N zadań jedno po drugim zostaną wykonane przez jeden wątek.
- Jeśli wątek zostanie przerwany, zostanie utworzony nowy wątek do wykonania pozostałych zadań.
Wyobraźmy sobie sytuację, w której nasz program wymaga następującej funkcjonalności:
Żądania od użytkowników musimy przetwarzać w ciągu 30 sekund, ale nie więcej niż jedno żądanie na jednostkę czasu.
Utwórz klasę zadania do przetwarzania żądania od użytkowników:
class Task implements Runnable {
private final int taskNumber;
public Task(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
}
Klasa modeluje zachowanie przetwarzania przychodzącego żądania i wyświetla jego numer.
Następnie w metodzie main tworzymy ExecutorService dla 1 wątku, w którym sekwencyjnie przetwarzamy przychodzące żądania. Ponieważ warunek wskazany „w ciągu 30 sekund”, dodajemy oczekiwanie na zakończenie 30 sekund, po czym wymuszamy zatrzymanie usługi ExecutorService .
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1_000; i++) {
executorService.execute(new Task(i));
}
executorService.awaitTermination(30, TimeUnit.SECONDS);
executorService.shutdownNow();
}
Podczas uruchamiania widzimy komunikaty przetwarzania danych wyjściowych w konsoli:
Przetworzone żądanie nr 1 w wątku id=16
Przetworzone żądanie nr 2 w wątku id=16
….
Przetworzone żądanie nr 29 w wątku id=16
Po przetworzeniu żądań przez 30 sekund usługa executorService wywoła metodę shutdownNow() , która zatrzyma bieżące zadanie (które jest uruchomione) i anuluje wszystkie oczekujące zadania. Następnie program pomyślnie kończy swoje wykonanie.
Ale nie zawsze wszystko jest takie idealne, ponieważ w pracy programu łatwo może dojść do sytuacji, w której jedno z zadań, które trafia do naszego jedynego wątku w puli, nie zadziała poprawnie, a nawet zakończy nasz wątek. Możemy zasymulować taką sytuację, aby dowiedzieć się, jak executorService z pojedynczym wątkiem będzie działać w tym przypadku.
W tym celu na etapie wykonywania jednego z zadań kończymy nasz wątek za pomocą niebezpiecznej i przestarzałej metody Thread.currentThread().stop() . Robimy to celowo, aby zasymulować sytuację, w której wątek kończy się na jednym z zadań.
Zmieniamy naszą metodę uruchamiania w klasie Task :
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
if (taskNumber == 5) {
Thread.currentThread().stop();
}
System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
Przerwiemy przy zadaniu nr 5.
Zobaczmy, jak wygląda wynik z przerwaniem wątku na końcu zadania nr 5:
Przetworzone żądanie nr 1 w id wątku = 16
Przetworzone żądanie nr 2 w id wątku = 16
Przetworzone żądanie nr 3 w id wątku = 16 Przetworzone żądanie nr
4 w id wątku = 16
Przetworzone żądanie nr 6 w id wątku=17
Przetworzone żądanie nr 7 w id wątku=17
…
Przetworzone żądanie nr 29 w id wątku=17
Widzimy, że po przerwaniu wątku na końcu zadania 5 zadania zaczynają być wykonywane w wątku o id = 17, chociaż przed tym zadaniem były wykonywane w wątku o id = 16. A ponieważ jest tylko jeden wątku w naszej puli, to może oznaczać tylko jedno - executorService zastąpił zatrzymany wątek nowym i zadania działały dalej.
Zatem użycie newSingleThreadExecutor do pracy z pulą z pojedynczym wątkiem przetwarzania zadań jest konieczne dla zadań sekwencyjnych, które implikują wykonanie tylko jednego zadania na raz, ale jednocześnie wymagają kontynuacji zadań przetwarzania z kolejki , pomimo wyniku wykonania zadania (przypadek, gdy w jednym z zadań wątek może zostać zabity).
Fabryka wątków
Mówiąc o tworzeniu i odtwarzaniu wątków, nie możemy nie wspomniećFabryka wątków.
Fabryka wątkówjest obiektem, który tworzy nowe wątki na żądanie.
Możemy stworzyć własną fabrykę tworzenia wątków i przekazać jej instancję do metody Executors.newSingleThreadExecutor(ThreadFactory threadFactory) .
|
Zastąp metodę tworzenia nowego wątku, przekazując nazwę do konstruktora. |
|
Zmieniono nazwę i priorytet utworzonego wątku. |
W ten sposób dowiedzieliśmy się, że istnieją 2 przeciążone metody Executors.newSingleThreadExecutor . Jeden - bez parametrów, drugi - z parametrem typu ThreadFactory .
Używając ThreadFactory , możesz wprowadzić różne ustawienia dla tworzonych wątków, na przykład ustawić priorytety, użyć podklas wątków, dodać wątek UncaughtExceptionHandler i tak dalej.
GO TO FULL VERSION