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.

Java SE 8
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:

  1. 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.

  2. Możesz użyć istniejących lub utworzyć nowe zasady obsługi odrzucania.

    1. W domyślnym ThreadPoolExecutor.AbortPolicy program obsługi zgłasza środowisko wykonawcze RejectedExecutionException po odrzuceniu.

    2. 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ń.

    3. W ThreadPoolExecutor.DiscardPolicy zadanie, którego nie można wykonać, jest po prostu usuwane.

    4. 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).

  3. Niestandardowe ThreadFactory można skonfigurować, co jest przydatne:

    1. Aby ustawić bardziej opisową nazwę wątku
    2. Aby ustawić status demona wątku
    3. 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

  1. próbujesz przesłać zadania do Shutdown Executora lub
  2. 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

  1. ExecutorService

    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 ograniczenia CPU/Memory , wolę używać ThreadPoolExecutor z ograniczeniami pojemności i RejectedExecutionHandler do obsługi odrzucania zadań.

  2. CountDownLatch

    CountDownLatch zostanie zainicjowany z podaną liczbą. Liczba ta jest zmniejszana przez wywołania metody countDown() . Wątki oczekujące na osiągnięcie tego zera mogą wywoływać jedną z metod await() . Wywołanie await() 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:

    1. 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ść

    2. Poczekaj na ukończenie N wątków przed rozpoczęciem wykonywania

    3. Wykrywanie zakleszczenia.

  1. 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 .

    1. ThreadPoolExecutor.AbortPolicy , program obsługi zgłasza środowisko wykonawcze RejectedExecutionException po odrzuceniu.

    2. 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ń.

    3. W ThreadPoolExecutor.DiscardPolicy zadanie, którego nie można wykonać, jest po prostu usuwane.

    4. 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() .

  1. Kolejnym mechanizmem, którego nie zacytowałeś, jest ForkJoinPool

    ForkJoinPool został dodany do Java w Javie 7. ForkJoinPool jest podobny do Java ExecutorService ale z jedną różnicą. ForkJoinPool ułatwia podział zadań na mniejsze zadania, które są następnie przesyłane również do ForkJoinPool . Kradzież zadań ma miejsce w ForkJoinPool gdy wolne wątki ForkJoinPool 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 i RecursiveAction ale nadal możesz korzystać z ForkJoinPool .

    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

  1. 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;
        }
    }
}
  1. CountDownLatch

    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 .

  2. ForkJoinPool lub newWorkStealingPool() w Executors

  3. Iteruj przez wszystkie Future obiekty utworzone po przesłaniu do ExecutorService

  4. 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.

  1. 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) i newSingleThreadExecutor() 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 dla newSingleThreadExecutor

    Przypadków użycia:

    1. Chcesz wykonać przesłane zadania w sekwencji.
    2. Potrzebujesz tylko jednego wątku, aby obsłużyć wszystkie Twoje żądania

    Cons:

    1. Nieograniczona kolejka jest szkodliwa
  2. 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:

    1. Efektywne wykorzystanie dostępnych rdzeni. Skonfiguruj nThreads jako Runtime.getRuntime().availableProcessors() nThreads Runtime.getRuntime().availableProcessors()
    2. Gdy zdecydujesz, że liczba wątków nie powinna przekraczać liczby w puli wątków

    Cons:

    1. Nieograniczona kolejka jest szkodliwa.
  3. 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:

    1. Do krótkotrwałych zadań asynchronicznych

    Cons:

    1. Nieograniczona kolejka jest szkodliwa.
    2. 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
  1. 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:

    1. 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:

    1. 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:

    1. Do dzielenia i podbijania problemów.
    2. Efektywne wykorzystanie wolnych obrotów. Bezczynne wątki kradną zadania z zajętych wątków.

    Cons:

    1. 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

  1. Dynamicznie kontroluj wielkość puli wątków
  2. Ustaw pojemność BlockingQueue
  3. Zdefiniuj RejectionExecutionHander gdy kolejka jest pełna
  4. 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() .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow