ExecutorService - Menunggu Utas Selesai

1. Ikhtisar

The ExecutorService kerangka memudahkan untuk memproses tugas-tugas di beberapa thread. Kami akan memberikan contoh beberapa skenario di mana kami menunggu utas menyelesaikan eksekusinya.

Selain itu, kami akan menunjukkan cara mematikan ExecutorService dengan baik dan menunggu thread yang sudah berjalan untuk menyelesaikan eksekusinya.

2. Setelah Executor's Shutdown

Saat menggunakan Executor, kita bisa mematikannya dengan memanggil metode shutdown () atau shutdownNow () . Meskipun, itu tidak akan menunggu sampai semua utas berhenti dijalankan.

Menunggu utas yang ada untuk menyelesaikan eksekusinya dapat dilakukan dengan menggunakan metode awaitTermination () .

Ini memblokir utas hingga semua tugas menyelesaikan eksekusinya atau waktu tunggu yang ditentukan tercapai:

public void awaitTerminationAfterShutdown(ExecutorService threadPool) { threadPool.shutdown(); try { if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { threadPool.shutdownNow(); } } catch (InterruptedException ex) { threadPool.shutdownNow(); Thread.currentThread().interrupt(); } }

3. Menggunakan CountDownLatch

Selanjutnya, mari kita lihat pendekatan lain untuk memecahkan masalah ini - menggunakan CountDownLatch untuk memberi sinyal penyelesaian tugas.

Kita bisa menginisialisasinya dengan nilai yang mewakili berapa kali ia bisa dikurangi sebelum semua utas, yang telah memanggil metode await () , diberi tahu.

Misalnya, jika kita membutuhkan thread saat ini untuk menunggu N thread lain menyelesaikan eksekusinya, kita dapat menginisialisasi latch menggunakan N :

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); CountDownLatch latch = new CountDownLatch(2); for (int i = 0; i  { try { // ... latch.countDown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // wait for the latch to be decremented by the two remaining threads latch.await();

4. Menggunakan invokeAll ()

Pendekatan pertama yang bisa kita gunakan untuk menjalankan utas adalah metode invokeAll () . Metode ini mengembalikan daftar objek Future setelah semua tugas selesai atau batas waktu habis .

Selain itu, kita harus mencatat bahwa urutan objek Future yang dikembalikan sama dengan daftar objek Callable yang disediakan :

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); List
    
      callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); long startProcessingTime = System.currentTimeMillis(); List
     
       futures = WORKER_THREAD_POOL.invokeAll(callables); awaitTerminationAfterShutdown(WORKER_THREAD_POOL); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue(totalProcessingTime >= 3000); String firstThreadResponse = futures.get(0).get(); assertTrue("fast thread".equals(firstThreadResponse)); String secondThreadResponse = futures.get(1).get(); assertTrue("slow thread".equals(secondThreadResponse));
     
    

5. Menggunakan ExecutorCompletionService

Pendekatan lain untuk menjalankan beberapa utas adalah dengan menggunakan ExecutorCompletionService. Ini menggunakan ExecutorService yang disediakan untuk menjalankan tugas.

Satu perbedaan atas invokeAll () adalah urutan di mana Futures, mewakili tugas-tugas yang dijalankan dikembalikan. ExecutorCompletionService menggunakan antrian untuk menyimpan hasil dalam urutan penyelesaiannya , sementara invokeAll () mengembalikan daftar yang memiliki urutan sekuensial yang sama seperti yang dihasilkan oleh iterator untuk daftar tugas yang diberikan:

CompletionService service = new ExecutorCompletionService(WORKER_THREAD_POOL); List
    
      callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); for (Callable callable : callables) { service.submit(callable); } 
    

Hasilnya dapat diakses menggunakan metode take () :

long startProcessingTime = System.currentTimeMillis(); Future future = service.take(); String firstThreadResponse = future.get(); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue("First response should be from the fast thread", "fast thread".equals(firstThreadResponse)); assertTrue(totalProcessingTime >= 100 && totalProcessingTime = 3000 && totalProcessingTime < 4000); LOG.debug("Thread finished after: " + totalProcessingTime + " milliseconds"); awaitTerminationAfterShutdown(WORKER_THREAD_POOL);

6. Kesimpulan

Bergantung pada kasus penggunaan, kami memiliki berbagai opsi untuk menunggu utas menyelesaikan eksekusinya.

Sebuah CountDownLatch berguna ketika kita perlu sebuah mekanisme untuk memberitahu satu atau lebih benang yang satu set operasi yang dilakukan oleh benang lain telah selesai.

ExecutorCompletionService berguna ketika kita perlu mengakses hasil tugas sesegera mungkin dan pendekatan lain ketika kita ingin menunggu semua tugas yang sedang berjalan selesai.

Kode sumber artikel tersedia di GitHub.