Java Language
Executor, ExecutorService i Thread pule
Szukaj…
Wprowadzenie
Interfejs Executora w Javie umożliwia oddzielenie wysyłania zadań od mechaniki uruchamiania każdego zadania, w tym szczegółów użycia wątku, planowania itp. Zwykle zamiast jawnego tworzenia wątków używa się Executora. Dzięki Executors programiści nie będą musieli znacznie przepisywać kodu, aby móc łatwo dostroić zasady wykonywania zadań w programie.
Uwagi
Pułapki
- Podczas planowania zadania do powtarzalnego wykonania, w zależności od użytej usługi ScheduledExecutorService, zadanie może zostać zawieszone z dalszego wykonywania, jeśli wykonanie zadania spowoduje wyjątek, który nie jest obsługiwany. Zobacz Mother F ** k the ScheduledExecutorService!
Fire and Forget - Runnable Tasks
Wykonawcy akceptują kod java.lang.Runnable
który zawiera (potencjalnie obliczeniowy lub w inny sposób długo działający lub ciężki) kod do uruchomienia w innym wątku.
Wykorzystanie byłoby:
Executor exec = anExecutor;
exec.execute(new Runnable() {
@Override public void run() {
//offloaded work, no need to get result back
}
});
Zauważ, że z tym executorem nie masz możliwości odzyskania żadnej obliczonej wartości.
W Javie 8 można wykorzystać lambdas do skrócenia przykładu kodu.
Executor exec = anExecutor;
exec.execute(() -> {
//offloaded work, no need to get result back
});
ThreadPoolExecutor
Często stosowanym executorem jest ThreadPoolExecutor
, który zajmuje się obsługą wątków. Możesz skonfigurować minimalną liczbę wątków, które zawsze musi utrzymywać moduł wykonujący, gdy nie ma wiele do zrobienia (nazywa się to rozmiarem rdzenia) i maksymalny rozmiar wątków, do którego Pula może się zwiększyć, jeśli jest więcej do zrobienia. Po zmniejszeniu obciążenia pula powoli zmniejsza liczbę wątków, aż osiągnie minimalny rozmiar.
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, // keep at least one thread ready,
// even if no Runnables are executed
5, // at most five Runnables/Threads
// executed in parallel
1, TimeUnit.MINUTES, // idle Threads terminated after one
// minute, when min Pool size exceeded
new ArrayBlockingQueue<Runnable>(10)); // outstanding Runnables are kept here
pool.execute(new Runnable() {
@Override public void run() {
//code to run
}
});
Uwaga: Jeśli skonfigurujesz ThreadPoolExecutor
z niezwiązaną kolejką, liczba wątków nie przekroczy corePoolSize
ponieważ nowe wątki są tworzone tylko wtedy, gdy kolejka jest pełna:
ThreadPoolExecutor ze wszystkimi parametrami:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
z JavaDoc
Jeśli działa więcej niż wątek corePoolSize, ale mniej niż maximumPoolSize, nowy wątek zostanie utworzony tylko wtedy, gdy kolejka jest pełna.
Zalety:
Blokowanie wielkości kolejki może być kontrolowane i można uniknąć scenariuszy braku pamięci. Wydajność aplikacji nie zostanie obniżona przy ograniczonym ograniczonym rozmiarze kolejki.
Możesz użyć istniejących lub utworzyć nowe zasady obsługi odrzucania.
W domyślnym ThreadPoolExecutor.AbortPolicy program obsługi zgłasza środowisko wykonawcze RejectedExecutionException po odrzuceniu.
W
ThreadPoolExecutor.CallerRunsPolicy
wątek, który wywołuje wykonanie, sam uruchamia zadanie. Zapewnia to prosty mechanizm kontroli sprzężenia zwrotnego, który spowolni tempo przesyłania nowych zadań.W
ThreadPoolExecutor.DiscardPolicy
zadanie, którego nie można wykonać, jest po prostu usuwane.W
ThreadPoolExecutor.DiscardOldestPolicy
, jeśli executor nie zostanie zamknięty, zadanie na początku kolejki roboczej zostanie porzucone, a następnie ponowna próba wykonania (co może się nie powieść, co spowoduje powtórzenie tego).
Niestandardowe
ThreadFactory
można skonfigurować, co jest przydatne:- Aby ustawić bardziej opisową nazwę wątku
- Aby ustawić status demona wątku
- Aby ustawić priorytet wątku
Oto przykład użycia ThreadPoolExecutor
Pobieranie wartości z obliczeń - na żądanie
Jeśli obliczenia generują pewną wartość zwracaną, która jest później wymagana, proste zadanie Runnable nie jest wystarczające. W takich przypadkach można użyć ExecutorService.submit(
Callable
<T>)
który zwraca wartość po zakończeniu wykonywania.
Usługa zwróci Future
której możesz użyć do odzyskania wyniku wykonania zadania.
// Submit a callable for execution
ExecutorService pool = anExecutorService;
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override public Integer call() {
//do some computation
return new Random().nextInt();
}
});
// ... perform other tasks while future is executed in a different thread
Jeśli chcesz uzyskać wynik przyszłości, wywołaj future.get()
Poczekaj w nieskończoność, aż przyszłość zakończy się wynikiem.
try { // Blocks current thread until future is completed Integer result = future.get(); catch (InterruptedException || ExecutionException e) { // handle appropriately }
Poczekaj, aż przyszłość się zakończy, ale nie dłużej niż określony czas.
try { // Blocks current thread for a maximum of 500 milliseconds. // If the future finishes before that, result is returned, // otherwise TimeoutException is thrown. Integer result = future.get(500, TimeUnit.MILLISECONDS); catch (InterruptedException || ExecutionException || TimeoutException e) { // handle appropriately }
Jeśli wynik zaplanowanego lub uruchomionego zadania nie jest już wymagany, możesz wywołać Future.cancel(boolean)
aby go anulować.
- Wywołanie
cancel(false)
spowoduje usunięcie zadania z kolejki zadań do uruchomienia. - Wywołanie
cancel(true)
spowoduje również przerwanie zadania, jeśli jest ono aktualnie uruchomione.
Planowanie zadań do uruchomienia w ustalonym czasie, z opóźnieniem lub wielokrotnie
Klasa ScheduledExecutorService
zapewnia metody planowania pojedynczego lub powtarzanego zadania na wiele sposobów. W poniższym przykładzie kodu założono, że pool
została zadeklarowana i zainicjowana w następujący sposób:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
Oprócz zwykłych metod ExecutorService
interfejs API ScheduledExecutorService
dodaje 4 metody, które planują zadania i zwracają obiekty ScheduledFuture
. Ten ostatni może być wykorzystywany do pobierania wyników (w niektórych przypadkach) i anulowania zadań.
Rozpoczęcie zadania po ustalonym opóźnieniu
Poniższy przykład planuje uruchomienie zadania po dziesięciu minutach.
ScheduledFuture<Integer> future = pool.schedule(new Callable<>() {
@Override public Integer call() {
// do something
return 42;
}
},
10, TimeUnit.MINUTES);
Rozpoczęcie zadań ze stałą stawką
Poniższy przykład planuje rozpoczęcie zadania po dziesięciu minutach, a następnie powtarzanie z częstotliwością raz na minutę.
ScheduledFuture<?> future = pool.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
// do something
}
},
10, 1, TimeUnit.MINUTES);
Wykonywanie zadania będzie kontynuowane zgodnie z harmonogramem, dopóki pool
zostanie zamknięta, future
zostanie anulowana lub jedno z zadań napotka wyjątek.
Gwarantujemy, że zadania zaplanowane przez dane wywołanie scheduledAtFixedRate
nie będą się nakładać w czasie. Jeśli zadanie trwa dłużej niż przewidziany okres, kolejne i kolejne wykonywanie zadań może rozpocząć się z opóźnieniem.
Rozpoczęcie zadań z ustalonym opóźnieniem
Poniższy przykład planuje uruchomienie zadania po dziesięciu minutach, a następnie kilkakrotnie z opóźnieniem jednej minuty między zakończeniem jednego zadania a rozpoczęciem kolejnego.
ScheduledFuture<?> future = pool.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
// do something
}
},
10, 1, TimeUnit.MINUTES);
Wykonywanie zadania będzie kontynuowane zgodnie z harmonogramem, dopóki pool
zostanie zamknięta, future
zostanie anulowana lub jedno z zadań napotka wyjątek.
Obsługa odrzuconego wykonania
Gdyby
- próbujesz przesłać zadania do Shutdown Executora lub
- kolejka jest nasycona (możliwe tylko z ograniczonymi) i osiągnięto maksymalną liczbę wątków,
Zostanie wywołana RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)
.
Domyślne zachowanie polega na tym, że zostanie wywołany wyjątek RejectedExecutionException na osobę dzwoniącą. Dostępne są jednak bardziej predefiniowane zachowania:
- ThreadPoolExecutor.AbortPolicy (domyślnie wyrzuci REE)
- ThreadPoolExecutor.CallerRunsPolicy (wykonuje zadanie na wątku dzwoniącego - blokuje je )
- ThreadPoolExecutor.DiscardPolicy (dyskretnie odrzuć zadanie)
- ThreadPoolExecutor.DiscardOldestPolicy (dyskretnie odrzuć najstarsze zadanie w kolejce i ponów próbę wykonania nowego zadania)
Możesz ustawić je za pomocą jednego z konstruktorów ThreadPool:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) // <--
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) // <--
Możesz również zaimplementować własne zachowanie, rozszerzając interfejs RejectedExecutionHandler :
void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
różnice w obsłudze wyjątków submission () vs execute ()
Zasadniczo polecenie execute () służy do wywoływania ognia i zapominania (bez potrzeby analizowania wyniku), a polecenie subm () służy do analizy wyniku obiektu Future.
Powinniśmy zdawać sobie sprawę z kluczowej różnicy mechanizmów obsługi wyjątków między tymi dwoma poleceniami.
Wyjątki od submission () są połykane przez framework, jeśli ich nie złapałeś.
Przykład kodu, aby zrozumieć różnicę:
Przypadek 1: prześlij komendę Runnable with execute (), która zgłasza wyjątek.
import java.util.concurrent.*;
import java.util.*;
public class ExecuteSubmitDemo {
public ExecuteSubmitDemo() {
System.out.println("creating service");
ExecutorService service = Executors.newFixedThreadPool(2);
//ExtendedExecutor service = new ExtendedExecutor();
for (int i = 0; i < 2; i++){
service.execute(new Runnable(){
public void run(){
int a = 4, b = 0;
System.out.println("a and b=" + a + ":" + b);
System.out.println("a/b:" + (a / b));
System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
}
});
}
service.shutdown();
}
public static void main(String args[]){
ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
}
}
class ExtendedExecutor extends ThreadPoolExecutor {
public ExtendedExecutor() {
super(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
}
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}
wynik:
creating service
a and b=4:0
a and b=4:0
Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" java.lang.ArithmeticException: / by zero
at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.lang.ArithmeticException: / by zero
at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
Przypadek 2: Zamień execute () na subm (): service.submit(new Runnable(){
W tym przypadku wyjątki są połykane przez framework, ponieważ metoda run () ich nie złapała jawnie.
wynik:
creating service
a and b=4:0
a and b=4:0
Przypadek 3: Zmień newFixedThreadPool na ExtendedExecutor
//ExecutorService service = Executors.newFixedThreadPool(2);
ExtendedExecutor service = new ExtendedExecutor();
wynik:
creating service
a and b=4:0
java.lang.ArithmeticException: / by zero
a and b=4:0
java.lang.ArithmeticException: / by zero
Wykazałem, że ten przykład obejmuje dwa tematy: Użyj niestandardowego ThreadPoolExecutor i obsługuj Exectpion za pomocą niestandardowego ThreadPoolExecutor.
Inne proste rozwiązanie powyższego problemu: Gdy używasz normalnej komendy ExecutorService & submission, pobierz obiekt Future z wywołania komendy subm () get () API w Future. Złap trzy wyjątki, które zostały zacytowane w implementacji metody afterExecute. Przewaga niestandardowego ThreadPoolExecutor nad tym podejściem: Musisz obsługiwać mechanizm obsługi wyjątków tylko w jednym miejscu - Niestandardowy ThreadPoolExecutor.
Przypadki użycia dla różnych typów konstrukcji współbieżnych
ExecutorService executor = Executors.newFixedThreadPool(50);
Jest prosty i łatwy w użyciu. Ukrywa szczegóły niskiego poziomu
ThreadPoolExecutor
.Wolę ten, gdy liczba zadań
Callable/Runnable
jest niewielka, a zestawianie zadań w niepowiązanej kolejce nie zwiększa pamięci i nie obniża wydajności systemu. Jeśli masz ograniczeniaCPU/Memory
, wolę używaćThreadPoolExecutor
z ograniczeniami pojemności iRejectedExecutionHandler
do obsługi odrzucania zadań.CountDownLatch
zostanie zainicjowany z podaną liczbą. Liczba ta jest zmniejszana przez wywołania metodycountDown()
. Wątki oczekujące na osiągnięcie tego zera mogą wywoływać jedną z metodawait()
. Wywołanieawait()
blokuje wątek, aż liczba osiągnie zero. Ta klasa umożliwia wątkowi Java oczekiwanie, aż inny zestaw wątków wykona swoje zadania.Przypadków użycia:
Osiągnięcie maksymalnej równoległości: czasami chcemy rozpocząć wiele wątków w tym samym czasie, aby osiągnąć maksymalną równoległość
Poczekaj na ukończenie N wątków przed rozpoczęciem wykonywania
Wykrywanie zakleszczenia.
ThreadPoolExecutor : Zapewnia większą kontrolę. Jeśli aplikacja jest ograniczona przez liczbę oczekujących zadań Runnable / Callable, możesz użyć ograniczonej kolejki, ustawiając maksymalną pojemność. Gdy kolejka osiągnie maksymalną pojemność, możesz zdefiniować RejectionHandler. Java przewiduje cztery rodzaje
RejectedExecutionHandler
polityk .ThreadPoolExecutor.AbortPolicy
, program obsługi zgłasza środowisko wykonawcze RejectedExecutionException po odrzuceniu.ThreadPoolExecutor.CallerRunsPolicy`, wątek, który wywołuje wykonanie, sam uruchamia zadanie. Zapewnia to prosty mechanizm kontroli sprzężenia zwrotnego, który spowolni tempo przesyłania nowych zadań.
W
ThreadPoolExecutor.DiscardPolicy
zadanie, którego nie można wykonać, jest po prostu usuwane.ThreadPoolExecutor.DiscardOldestPolicy
, jeśli executor nie zostanie zamknięty, zadanie na początku kolejki roboczej zostanie odrzucone, a następnie ponowna próba wykonania (co może się nie powieść, powodując powtórzenie tego).
Jeśli chcesz symulować zachowanie CountDownLatch
, możesz użyć metody invokeAll()
.
Kolejnym mechanizmem, którego nie zacytowałeś, jest ForkJoinPool
ForkJoinPool
został dodany do Java w Javie 7.ForkJoinPool
jest podobny do JavaExecutorService
ale z jedną różnicą.ForkJoinPool
ułatwia podział zadań na mniejsze zadania, które są następnie przesyłane również doForkJoinPool
. Kradzież zadań ma miejsce wForkJoinPool
gdy wolne wątkiForkJoinPool
kradną zadania z zajętej kolejki wątków roboczych.Java 8 wprowadziła jeszcze jeden interfejs API w ExecutorService, aby utworzyć pulę kradzieży pracy. Nie musisz tworzyć
RecursiveTask
iRecursiveAction
ale nadal możesz korzystać zForkJoinPool
.public static ExecutorService newWorkStealingPool()
Tworzy pulę wątków kradnącą pracę przy użyciu wszystkich dostępnych procesorów jako docelowego poziomu równoległości.
Domyślnie jako parametr przyjmuje liczbę rdzeni procesora.
Wszystkie te cztery mechanizmy wzajemnie się uzupełniają. W zależności od poziomu szczegółowości, który chcesz kontrolować, musisz wybrać odpowiednie.
Poczekaj na zakończenie wszystkich zadań w ExecutorService
Przyjrzyjmy się różnym opcjom oczekiwania na zakończenie zadań przesłanych do Executora
- ExecutorService
invokeAll()
Wykonuje podane zadania, zwracając listę kontraktów terminowych posiadających ich status i wyniki, gdy wszystko jest zakończone.
Przykład:
import java.util.concurrent.*;
import java.util.*;
public class InvokeAllDemo{
public InvokeAllDemo(){
System.out.println("creating service");
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<MyCallable> futureList = new ArrayList<MyCallable>();
for (int i = 0; i < 10; i++){
MyCallable myCallable = new MyCallable((long)i);
futureList.add(myCallable);
}
System.out.println("Start");
try{
List<Future<Long>> futures = service.invokeAll(futureList);
} catch(Exception err){
err.printStackTrace();
}
System.out.println("Completed");
service.shutdown();
}
public static void main(String args[]){
InvokeAllDemo demo = new InvokeAllDemo();
}
class MyCallable implements Callable<Long>{
Long id = 0L;
public MyCallable(Long val){
this.id = val;
}
public Long call(){
// Add your business logic
return id;
}
}
}
Pomoc w synchronizacji, która pozwala co najmniej jednemu wątkowi poczekać, aż zakończy się zestaw operacji wykonywanych w innych wątkach.
CountDownLatch jest inicjowany z podaną liczbą. Blokuje metody oczekujące, aż bieżąca liczba osiągnie zero z powodu wywołań metody
countDown()
, po czym wszystkie oczekujące wątki są zwalniane, a wszelkie kolejne wywołania oczekiwania natychmiast wracają. Jest to zjawisko jednorazowe - liczenia nie można zresetować. Jeśli potrzebujesz wersji, która resetuje licznik, rozważ użycie CyclicBarrier .ForkJoinPool lub
newWorkStealingPool()
w ExecutorsIteruj przez wszystkie
Future
obiekty utworzone po przesłaniu doExecutorService
Zalecany sposób zamknięcia ze strony dokumentacji Oracle w ExecutorService :
void shutdownAndAwaitTermination(ExecutorService pool) { pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!pool.awaitTermination(60, TimeUnit.SECONDS)) System.err.println("Pool did not terminate"); } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); }
shutdown():
inicjuje uporządkowane zamykanie, w którym wykonywane są wcześniej przesłane zadania, ale nowe zadania nie będą akceptowane.shutdownNow():
Próbuje zatrzymać wszystkie aktywnie wykonujące zadania, zatrzymuje przetwarzanie zadań oczekujących i zwraca listę zadań oczekujących na wykonanie.W powyższym przykładzie, jeśli wykonanie zadań zajmuje więcej czasu, możesz zmienić warunek if na warunek while
Zastąpić
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
z
while(!pool.awaitTermination(60, TimeUnit.SECONDS)) { Thread.sleep(60000);
}
Przypadki użycia dla różnych typów usługi ExecutorService
Executors zwraca różne typy ThreadPools dostosowane do konkretnych potrzeb.
public static ExecutorService newSingleThreadExecutor()
Tworzy moduł wykonujący, który używa pojedynczego wątku roboczego działającego na niezwiązanej kolejce
Istnieje różnica między
newFixedThreadPool(1)
inewSingleThreadExecutor()
jak mówi dokument Java dla tego ostatniego:W przeciwieństwie do równoważnego skądinąd newFixedThreadPool (1) zwróconego executora gwarantuje się, że nie będzie można go zrekonfigurować w celu użycia dodatkowych wątków.
Co oznacza, że
newFixedThreadPool
można ponownie skonfigurować w późniejszym programie:((ThreadPoolExecutor) fixedThreadPool).setMaximumPoolSize(10)
Nie jest to możliwe dlanewSingleThreadExecutor
Przypadków użycia:
- Chcesz wykonać przesłane zadania w sekwencji.
- Potrzebujesz tylko jednego wątku, aby obsłużyć wszystkie Twoje żądania
Cons:
- Nieograniczona kolejka jest szkodliwa
public static ExecutorService newFixedThreadPool(int nThreads)
Tworzy pulę wątków, która ponownie wykorzystuje stałą liczbę wątków działających na współużytkowanej niezwiązanej kolejce. W dowolnym momencie wątki nThreads będą aktywnymi zadaniami przetwarzania. Jeśli dodatkowe zadania zostaną przesłane, gdy wszystkie wątki są aktywne, będą czekać w kolejce, aż wątek będzie dostępny
Przypadków użycia:
- Efektywne wykorzystanie dostępnych rdzeni. Skonfiguruj
nThreads
jakoRuntime.getRuntime().availableProcessors()
nThreads
Runtime.getRuntime().availableProcessors()
- Gdy zdecydujesz, że liczba wątków nie powinna przekraczać liczby w puli wątków
Cons:
- Nieograniczona kolejka jest szkodliwa.
- Efektywne wykorzystanie dostępnych rdzeni. Skonfiguruj
public static ExecutorService newCachedThreadPool()
Tworzy pulę wątków, która w razie potrzeby tworzy nowe wątki, ale użyje wcześniej zbudowanych wątków, gdy będą one dostępne
Przypadków użycia:
- Do krótkotrwałych zadań asynchronicznych
Cons:
- Nieograniczona kolejka jest szkodliwa.
- Każde nowe zadanie utworzy nowy wątek, jeśli wszystkie istniejące wątki są zajęte. Jeśli zadanie trwa długo, zostanie utworzona większa liczba wątków, co obniży wydajność systemu. Alternatywne w tym przypadku:
newFixedThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Tworzy pulę wątków, która może planować uruchamianie poleceń po określonym opóźnieniu lub wykonywanie okresowe.
Przypadków użycia:
- Obsługa powtarzających się zdarzeń z opóźnieniami, które będą miały miejsce w przyszłości w określonych odstępach czasu
Cons:
- Nieograniczona kolejka jest szkodliwa.
5.
public static ExecutorService newWorkStealingPool()
Tworzy pulę wątków kradnącą pracę przy użyciu wszystkich dostępnych procesorów jako docelowego poziomu równoległości
Przypadków użycia:
- Do dzielenia i podbijania problemów.
- Efektywne wykorzystanie wolnych obrotów. Bezczynne wątki kradną zadania z zajętych wątków.
Cons:
- Nieograniczony rozmiar kolejki jest szkodliwy.
We wszystkich tych ExecutorService widać jedną wspólną wadę: kolejka niezwiązana. Zostanie to rozwiązane za pomocą ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Dzięki ThreadPoolExecutor
możesz
- Dynamicznie kontroluj wielkość puli wątków
- Ustaw pojemność
BlockingQueue
- Zdefiniuj
RejectionExecutionHander
gdy kolejka jest pełna -
CustomThreadFactory
aby dodać dodatkowe funkcje podczas tworzenia wątku(public Thread newThread(Runnable r)
Korzystanie z pul wątków
Pule wątków są najczęściej używane do wywoływania metod w ExecutorService
.
Do przesłania pracy do wykonania można użyć następujących metod:
metoda | Opis |
---|---|
submit | Wykonuje przesłaną pracę i zwraca przyszłość, którą można wykorzystać do uzyskania wyniku |
execute | Wykonaj zadanie w przyszłości, nie otrzymując żadnej wartości zwracanej |
invokeAll | Wykonaj listę zadań i zwróć listę kontraktów futures |
invokeAny | Wykonuje wszystkie, ale zwraca tylko wynik tego, który się powiódł (bez wyjątków) |
Po zakończeniu korzystania z puli wątków można wywołać polecenie shutdown()
aby zakończyć pulę wątków. Wykonuje to wszystkie oczekujące zadania. Aby poczekać na wykonanie wszystkich zadań, możesz zapętlić się w awaitTermination
na awaitTermination
lub isShutdown()
.