Bakit kailangan mo ang Executor interface?

Bago ang Java 5, kailangan mong isulat ang lahat ng iyong sariling pamamahala sa thread ng code sa iyong aplikasyon. Bilang karagdagan, ang paglikha ng isangbagong Threadobject ay isang resource-intensive na operasyon, at hindi makatuwirang gumawa ng bagong thread para sa bawat magaan na gawain. At dahil pamilyar ang problemang ito sa ganap na bawat developer ng mga multi-threaded na application, nagpasya silang dalhin ang functionality na ito sa Java bilang framework ng Executor .

Ano ang malaking ideya? Ito ay simple: sa halip na lumikha ng isang bagong thread para sa bawat bagong gawain, ang mga thread ay pinananatili sa isang uri ng "imbakan", at kapag dumating ang isang bagong gawain, kumukuha kami ng isang umiiral na thread sa halip na lumikha ng bago.

Ang mga pangunahing interface ng framework na ito ay Executor , ExecutorService at ScheduledExecutorService , bawat isa ay nagpapalawak ng functionality ng nauna.

Ang Interface ng Executor ay ang base interface. Nagdedeklara ito ng isang paraan ng void execute(Runnable command) na ipinapatupad ng isang Runnable object.

Ang interface ng ExecutorService ay mas kawili-wili. Mayroon itong mga pamamaraan para sa pamamahala sa pagkumpleto ng trabaho, pati na rin mga pamamaraan para sa pagbabalik ng ilang uri ng resulta. Tingnan natin ang mga pamamaraan nito nang mas malapitan:

Pamamaraan Paglalarawan
void shutdown(); Ang pagtawag sa paraang ito ay huminto sa ExecutorService . Ang lahat ng mga gawain na naisumite na para sa pagproseso ay makukumpleto, ngunit ang mga bagong gawain ay hindi tatanggapin.
List<Runnable> shutdownNow();

Ang pagtawag sa paraang ito ay huminto sa ExecutorService . Tatawagin ang Thread.interrupt para sa lahat ng mga gawain na naisumite na para sa pagproseso. Ang pamamaraang ito ay nagbabalik ng isang listahan ng mga naka-queue na gawain.

Ang pamamaraan ay hindi naghihintay para sa pagkumpleto ng lahat ng mga gawain na "nagpapatuloy" sa oras na tinawag ang pamamaraan.

Babala: Ang pagtawag sa paraang ito ay maaaring mag-leak ng mga mapagkukunan.

boolean isShutdown(); Sinusuri kung ang ExecutorService ay huminto.
boolean isTerminated(); Nagbabalik ng true kung nakumpleto ang lahat ng gawain kasunod ng pagsara ng ExecutorService . Hanggang sa tawagin ang shutdown() o shutdownNow() , palagi itong magbabalik ng false .
boolean awaitTermination(mahabang timeout, TimeUnit unit) throws InterruptedException;

Pagkatapos tawagin ang pamamaraan ng shutdown() , hinaharangan ng pamamaraang ito ang thread kung saan ito tinawag, hanggang sa maging totoo ang isa sa mga sumusunod na kundisyon:

  • kumpleto ang lahat ng naka-iskedyul na gawain;
  • lumipas na ang timeout na ipinasa sa pamamaraan;
  • ang kasalukuyang thread ay nagambala.

Nagbabalik ng true kung kumpleto na ang lahat ng gawain, at false kung lumipas ang timeout bago matapos.

<T> Hinaharap<T> isumite (Callable<T> gawain);

Nagdaragdag ng Callable na gawain sa ExecutorService at nagbabalik ng object na nagpapatupad ng interface sa Hinaharap .

Ang <T> ay ang uri ng resulta ng naipasa na gawain.

<T> Hinaharap<T> isumite (Runnable task, T resulta);

Nagdaragdag ng Runnable na gawain sa ExecutorService at nagbabalik ng object na nagpapatupad ng interface sa Hinaharap .

Ang parameter ng resulta ng T ay kung ano ang ibabalik sa pamamagitan ng isang tawag sa get() na pamamaraan sa resultaHinaharap na bagay.

Hinaharap<?> isumite(Runnable task);

Nagdaragdag ng Runnable na gawain sa ExecutorService at nagbabalik ng object na nagpapatupad ng interface sa Hinaharap .

Kung tatawagin natin ang get() method sa resultang Future object, pagkatapos ay makukuha natin ang null.

Ang <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

Nagpapasa ng listahan ng mga Callable na gawain sa ExecutorService . Nagbabalik ng listahan ng mga Futures kung saan makukuha natin ang resulta ng trabaho. Ibinabalik ang listahang ito kapag natapos na ang lahat ng isinumiteng gawain.

Kung ang koleksyon ng mga gawain ay binago habang tumatakbo ang pamamaraan, ang resulta ng pamamaraang ito ay hindi natukoy.

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

Nagpapasa ng listahan ng mga Callable na gawain sa ExecutorService . Nagbabalik ng listahan ng mga Futures kung saan makukuha natin ang resulta ng trabaho. Ibinabalik ang listahang ito kapag natapos na ang lahat ng naipasa na gawain, o pagkatapos na lumipas ang timeout sa pamamaraan, alinman ang mauna.

Kung lumipas ang timeout, kakanselahin ang mga hindi natapos na gawain.

Tandaan: Posible na ang isang kinanselang gawain ay hindi titigil sa pagtakbo (makikita natin ang side effect na ito sa halimbawa).

Kung ang koleksyon ng mga gawain ay binago habang tumatakbo ang pamamaraan, ang resulta ng pamamaraang ito ay hindi natukoy.

<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

Nagpapasa ng listahan ng mga Callable na gawain sa ExecutorService . Ibinabalik ang resulta ng isa sa mga gawain (kung mayroon man) na natapos nang walang pagbubukod (kung mayroon man).

Kung ang koleksyon ng mga gawain ay binago habang tumatakbo ang pamamaraan, ang resulta ng pamamaraang ito ay hindi natukoy.

<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

Nagpapasa ng listahan ng mga Callable na gawain sa ExecutorService . Ibinabalik ang resulta ng isa sa mga gawain (kung mayroon man) na nakumpleto nang walang pagbubukod bago lumipas ang timeout sa pamamaraan.

Kung ang koleksyon ng mga gawain ay binago habang tumatakbo ang pamamaraan, ang resulta ng pamamaraang ito ay hindi natukoy.

Tingnan natin ang isang maliit na halimbawa ng pagtatrabaho sa 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;
       }
   }
}

Output:

tapos
tapos
Natanggap na
ang tulog 1: naputol ang pagtulog
1: naputol ang tulog
tapos
tapos na
totoo

Ang bawat gawain ay tumatakbo nang 5 segundo. Gumawa kami ng pool para sa dalawang thread, kaya ang unang dalawang linya ng output ay may perpektong kahulugan.

Anim na segundo pagkatapos magsimula ang programa, ang invokeAll na pamamaraan ay nag-time out at ang resulta ay ibinalik bilang isang listahan ng Futures . Ito ay makikita mula sa output string Futures na natanggap .

Matapos magawa ang unang dalawang gawain, magsisimula pa ang dalawa. Ngunit dahil lumipas ang timeout na itinakda sa invokeAll na paraan, ang dalawang gawaing ito ay walang oras upang makumpleto. Nakatanggap sila ng utos na "kanselahin" . Iyon ang dahilan kung bakit ang output ay may dalawang linya na may sleep 1: sleep interrupted .

At pagkatapos ay makakakita ka ng dalawa pang linya na may tapos na . Ito ang side effect na binanggit ko noong inilalarawan ang invokeAll method.

Ang ikalimang at panghuling gawain ay hindi pa nasisimulan, kaya wala kaming nakikitang anuman tungkol dito sa output.

Ang huling dalawang linya ay ang resulta ng pagtawag sa isShutdown at isTerminated na pamamaraan.

Interesante ding patakbuhin ang halimbawang ito sa debug mode at tingnan ang status ng gawain pagkatapos lumipas ang timeout (magtakda ng breakpoint sa linya gamit ang executorService.shutdown(); ):

Nakita namin na dalawang gawain ang Nakumpleto nang normal , at tatlong gawain ang "Kinansela" .

ScheduledExecutorService

Upang tapusin ang aming talakayan ng mga tagapagpatupad, tingnan natin ang ScheduledExecutorService .

Mayroon itong 4 na pamamaraan:

Pamamaraan Paglalarawan
pampublikong ScheduledFuture<?> iskedyul(Runnable command, mahabang pagkaantala, TimeUnit unit); Iniiskedyul ang naipasa na Runnable na gawain upang tumakbo nang isang beses pagkatapos ng pagkaantala na tinukoy bilang isang argumento.
pampublikong iskedyul ng <V> ScheduledFuture<V>(Callable<V> callable, mahabang pagkaantala, TimeUnit unit); Nag-iskedyul ng ipinasang Callable na gawain upang tumakbo nang isang beses pagkatapos ng pagkaantala na tinukoy bilang isang argumento.
pampublikong ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); Nag-iskedyul ng pana-panahong pagpapatupad ng naipasa na gawain, na isasagawa sa unang pagkakataon pagkatapos ng initialDelay , at ang bawat kasunod na pagtakbo ay magsisimula pagkatapos ng panahon .
pampublikong ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); Nag-iskedyul ng pana-panahong pagpapatupad ng naipasa na gawain, na isasagawa sa unang pagkakataon pagkatapos ng initialDelay , at ang bawat kasunod na pagtakbo ay magsisimula pagkatapos ng pagkaantala (ang panahon sa pagitan ng pagkumpleto ng nakaraang pagtakbo at pagsisimula ng kasalukuyang).