De ce aveți nevoie de interfața Executor?

Înainte de Java 5, trebuia să scrieți tot propriul dvs. management de fire de cod în aplicația dvs. În plus, crearea unuithread nouobiectul este o operațiune care necesită mult resurse și nu are sens să creați un fir nou pentru fiecare sarcină ușoară. Și pentru că această problemă este familiară absolut tuturor dezvoltatorilor de aplicații multi-threaded, ei au decis să aducă această funcționalitate în Java ca framework Executor .

Care este ideea cea mare? Este simplu: în loc să creăm un fir nou pentru fiecare sarcină nouă, firele sunt păstrate într-un fel de „stocare”, iar când sosește o sarcină nouă, recuperăm un fir existent în loc să creăm unul nou.

Principalele interfețe ale acestui cadru sunt Executor , ExecutorService și ScheduledExecutorService , fiecare dintre acestea extinzând funcționalitatea celui precedent.

Interfața Executor este interfața de bază. Acesta declară o singură metodă void execute (comandă Runnable) care este implementată de un obiect Runnable .

Interfața ExecutorService este mai interesantă . Are metode de gestionare a finalizării lucrărilor, precum și metode de returnare a unui fel de rezultat. Să aruncăm o privire mai atentă asupra metodelor sale:

Metodă Descriere
void shutdown(); Apelarea acestei metode oprește ExecutorService . Toate sarcinile care au fost deja trimise pentru procesare vor fi finalizate, dar sarcinile noi nu vor fi acceptate.
List<Runnable> shutdownNow();

Apelarea acestei metode oprește ExecutorService . Thread.interrupt va fi apelat pentru toate sarcinile care au fost deja trimise spre procesare. Această metodă returnează o listă de sarcini aflate în coadă.

Metoda nu așteaptă finalizarea tuturor sarcinilor care sunt „în desfășurare” la momentul apelării metodei.

Avertisment: Apelarea acestei metode poate pierde resurse.

boolean isShutdown(); Verifică dacă ExecutorService este oprit.
boolean este Terminat(); Returnează true dacă toate sarcinile au fost finalizate după oprirea ExecutorService . Până când shutdown() sau shutdownNow() este apelat, va returna întotdeauna false .
boolean awaitTermination (timeout lung, unitate TimeUnit) aruncă InterruptedException;

După ce metoda shutdown() este apelată, această metodă blochează firul pe care este apelată, până când una dintre următoarele condiții este îndeplinită:

  • toate sarcinile programate sunt finalizate;
  • a expirat timpul de expirare transmis metodei;
  • firul curent este întrerupt.

Returnează true dacă toate sarcinile sunt finalizate și false dacă expirarea expiră înainte de terminare.

<T> Submit<T> viitor(sarcină apelabilă<T>);

Adaugă o sarcină Callable la ExecutorService și returnează un obiect care implementează interfața Future .

<T> este tipul rezultatului sarcinii trecute.

<T> Trimitere<T> viitor(sarcină rulabilă, rezultat T);

Adaugă o sarcină Runnable la ExecutorService și returnează un obiect care implementează interfața Future .

Parametrul rezultat T este ceea ce este returnat de un apel la metoda get() pe rezultatulObiect viitor.

Submit<?> viitor(sarcină rulabilă);

Adaugă o sarcină Runnable la ExecutorService și returnează un obiect care implementează interfața Future .

Dacă apelăm metoda get() pe obiectul Future rezultat , atunci obținem null.

<T> List<Future<T>> invokeAll(Collection<? extins Callable<T>> tasks) aruncă InterruptedException;

Transmite o listă de sarcini apelabile către ExecutorService . Returnează o listă de Futures din care putem obține rezultatul lucrării. Această listă este returnată când toate sarcinile trimise sunt finalizate.

Dacă colecția de sarcini este modificată în timp ce metoda rulează, rezultatul acestei metode este nedefinit.

<T> List<Future<T>> invokeAll(Collection<? extins Callable<T>> tasks, long timeout, TimeUnit unit) aruncă InterruptedException;

Transmite o listă de sarcini apelabile către ExecutorService . Returnează o listă de Futures din care putem obține rezultatul lucrării. Această listă este returnată când toate sarcinile trecute sunt finalizate sau după expirarea timpului de expirare transmis metodei, oricare dintre acestea survine primul.

Dacă expirarea expiră, sarcinile neterminate sunt anulate.

Notă: Este posibil ca o sarcină anulată să nu se oprească (vom vedea acest efect secundar în exemplu).

Dacă colecția de sarcini este modificată în timp ce metoda rulează, rezultatul acestei metode este nedefinit.

<T> T invokeAny(Collection<? extinde Callable<T>> tasks) aruncă InterruptedException, ExecutionException;

Transmite o listă de sarcini apelabile către ExecutorService . Returnează rezultatul uneia dintre sarcinile (dacă există) care s-a finalizat fără a arunca o excepție (dacă există).

Dacă colecția de sarcini este modificată în timp ce metoda rulează, rezultatul acestei metode este nedefinit.

<T> T invokeAny(Collection<? extinde Callable<T>> tasks, timeout lung, TimeUnit unit) aruncă InterruptedException, ExecutionException, TimeoutException;

Transmite o listă de sarcini apelabile către ExecutorService . Returnează rezultatul uneia dintre sarcinile (dacă există) care s-a finalizat fără a arunca o excepție înainte de expirarea timpului de expirare transmis metodei.

Dacă colecția de sarcini este modificată în timp ce metoda rulează, rezultatul acestei metode este nedefinit.

Să ne uităm la un mic exemplu de lucru cu ExecutorService .


import java.util.List;
import java.util.concurrent.*;

public class ExecutorServiceTest {
   public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
       java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
       MyRunnable task1 = new MyRunnable();
       MyRunnable task2 = new MyRunnable();
       MyRunnable task3 = new MyRunnable();
       MyRunnable task4 = new MyRunnable();
       MyRunnable task5 = new MyRunnable();

       final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
       final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
       System.out.println("Futures received");

// Stop the ExecutorService
       executorService.shutdown();

       try {
           TimeUnit.SECONDS.sleep(3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       System.out.println(executorService.isShutdown());
       System.out.println(executorService.isTerminated());
   }

   public static class MyRunnable implements Callable<Void> {

       @Override
       public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
           try {
               TimeUnit.SECONDS.sleep(3);
           } catch (InterruptedException e) {
               System.out.println("sleep 1: " + e.getMessage());
           }
           try {
               TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
               System.out.println("sleep 2: " + e.getMessage());
           }
           System.out.println("done");
           return null;
       }
   }
}

Ieșire:

gata
făcut
Futuri primite
somn 1: somn întrerupt
somn 1: somn întrerupt
făcut
făcut
adevărat
adevărat

Fiecare sarcină rulează timp de 5 secunde. Am creat un pool pentru două fire, așa că primele două linii de ieșire au sens perfect.

La șase secunde după pornirea programului, metoda invokeAll expiră și rezultatul este returnat ca o listă de Futures . Acest lucru poate fi văzut din șirul de ieșire Futures primit .

După ce sunt terminate primele două sarcini, încep alte două. Dar, deoarece expiră timpul stabilit în metoda invokeAll , aceste două sarcini nu au timp să fie finalizate. Ei primesc o comandă „anulează” . De aceea ieșirea are două linii cu sleep 1: sleep întrerupt .

Și apoi puteți vedea încă două rânduri cu terminat . Acesta este efectul secundar pe care l-am menționat când am descris metoda invokeAll .

A cincea și ultima sarcină nici măcar nu începe niciodată, așa că nu vedem nimic despre ea în rezultat.

Ultimele două linii sunt rezultatul apelării metodelor isShutdown și isTerminated .

De asemenea, este interesant să rulați acest exemplu în modul de depanare și să priviți starea sarcinii după expirarea timpului (setați un punct de întrerupere pe linie cu executorService.shutdown(); ):

Vedem că două sarcini au fost finalizate în mod normal și trei sarcini au fost „Anulate” .

ScheduledExecutorService

Pentru a încheia discuția noastră despre executanți, să aruncăm o privire la ScheduledExecutorService .

Are 4 metode:

Metodă Descriere
programul public ScheduledFuture<?> (comandă rulabilă, întârziere lungă, unitate TimeUnit); Programează sarcina Runnable trecută să ruleze o dată după întârzierea specificată ca argument.
program public <V> ScheduledFuture<V> (Callable<V> apelabil, întârziere lungă, unitate TimeUnit); Programează sarcina Callable trecută să ruleze o dată după întârzierea specificată ca argument.
public ScheduledFuture<?> scheduleAtFixedRate(comandă care se poate executa, întârziere inițială lungă, perioadă lungă, unitate TimeUnit); Programează execuția periodică a sarcinii trecute, care va fi executată pentru prima dată după initialDelay , iar fiecare rulare ulterioară va începe după perioada .
public ScheduledFuture<?> scheduleWithFixedDelay (comandă rulabilă, lung initialDelay, lungă întârziere, unitate TimeUnit); Programează execuția periodică a sarcinii trecute, care va fi executată pentru prima dată după initialDelay , iar fiecare rulare ulterioară va începe după întârziere (perioada dintre finalizarea executării anterioare și începerea celei curente).