Pertimbangkan sebuah program sederhana:


public static void main(String[] args) throws Exception {
	// Create an ExecutorService with a fixed number of threads: three
	ExecutorService service = Executors.newFixedThreadPool(3);
 
	// Pass a simple Runnable task to the ExecutorService
	service.submit(() -> System.out.println("done"));
}

Menjalankan program menghasilkan keluaran konsol yang kami harapkan:

Selesai

Tapi ini tidak diikuti dengan output yang biasa kita lihat di IntelliJ IDEA:

Proses selesai dengan kode keluar 0

Kami biasanya melihat itu ketika sebuah program berakhir.

Mengapa itu terjadi?

Deskripsi metode newFixedThreadPool() memberi tahu kita bahwa utas yang dibuat menggunakan ExecutorService terus ada hingga dihentikan secara eksplisit. Itu berarti karena kita meneruskan tugas ke ExecutorService , sebuah utas dibuat untuk menjalankannya, dan utas itu terus ada bahkan setelah tugas selesai.

Berhenti di ExecutorService

Akibatnya, kita perlu "mematikan" (atau menghentikan) ExecutorService . Kita dapat melakukannya dengan dua cara:

  1. void shutdown() — setelah metode ini dipanggil, ExecutorService berhenti menerima pekerjaan baru. Semua tugas yang sebelumnya dikirimkan ke ExecutorService akan terus berjalan.

    
    public static void main(String[] args) throws Exception {
    ExecutorService service = Executors.newFixedThreadPool(3);
        	service.submit(() -> System.out.println("task 1"));
        	service.submit(() -> System.out.println("task 2"));
        	service.shutdown();
        	// A RejectedExecutionException will occur here
        	service.submit(() -> System.out.println("task 3"));
    }
    
  2. List<Runnable> shutdownNow() — Metode ini mencoba menghentikan pekerjaan yang sedang aktif. Tugas yang masih menunggu giliran akan dibuang dan dikembalikan sebagai daftar Runnables .

    
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        List<Runnable> runnables = service.shutdownNow();
        runnables.forEach(System.out::println);
    }
    

Keluaran:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Belum selesai, tugas = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Tugas terbungkus = Uji$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[Tidak selesai, tugas = java.util.concurrent.Executors$RunnableAdapter@66a29884[Tugas terbungkus = Uji$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[Belum selesai, tugas = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Proses selesai dengan kode keluar 0

Output akan berbeda dari satu run ke run lainnya. Ada 2 jenis garis di output:

  • Angka berarti ExecutorService berhasil memproses tugas yang sesuai, menampilkan nomor dari daftar yang kami gunakan untuk membuat tugas.

  • Hasil pemanggilan metode toString() pada objek FutureTask . Objek ini adalah tugas yang dikirimkan ke ExecutorService tetapi tidak diproses.

Outputnya memiliki nuansa lain yang menarik. Idealnya, pertama-tama kita akan melihat semua angka yang ditampilkan, diikuti oleh objek FutureTask . Tapi masalah sinkronisasi mengacaukan baris di output.

Metode lain

ExecutorService memiliki beberapa metode lain yang terkait dengan menghentikannya:

  1. boolean awaitTermination(long timeout, unit TimeUnit) — metode ini memblokir utas yang memanggilnya. Blok berakhir segera setelah salah satu dari tiga peristiwa berikut terjadi:

    • setelah metode shutdown() dipanggil, semua pekerjaan aktif dan semua tugas terjadwal telah dijalankan;
    • batas waktu yang ditentukan oleh parameter metode telah berlalu;
    • utas yang memanggil metode awaitTermination() dihentikan.

    Metode mengembalikan nilai true jika ExecutorService dihentikan sebelum batas waktu habis, dan salah jika batas waktu sudah lewat.

    
    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	service.shutdown();
    	System.out.println(service.awaitTermination(1, TimeUnit.MICROSECONDS));
    }
    
  2. boolean isShutdown() — Mengembalikan nilai true jika metode shutdown() atau shutdownNow() telah dipanggil di ExecutorService .

    
    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	System.out.println(service.isShutdown());
    	service.shutdown();
    	System.out.println(service.isShutdown());
    }
    
  3. boolean isTerminated() — Mengembalikan nilai true jika metode shutdown() atau shutdownNow() telah dipanggil di ExecutorService dan semua tugas selesai.

    
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        service.shutdownNow();
        System.out.println(service.isTerminated());
    }
    

Contoh kode yang menggunakan metode ini:


public static void main(String[] args) throws Exception {
   ExecutorService service = Executors.newFixedThreadPool(16);
   Callable<String> task = () -> {
       Thread.sleep(1);
       return "Done";
   };
 
   // Add 10,000 tasks to the queue
   List<Future<String>> futures = IntStream.range(0, 10_000)
           .mapToObj(i -> service.submit(task))
           .collect(Collectors.toList());
   System.out.printf("%d tasks were submitted for execution.%n", futures.size());
 
   // Attempt to shut down
   service.shutdown();
   // Wait 100 milliseconds to finish the work
   if (service.awaitTermination(100, TimeUnit.MILLISECONDS)) {
       System.out.println("All tasks completed!");
   } else {
       // Stop forcibly
       List<Runnable> notExecuted = service.shutdownNow();
       System.out.printf("%d tasks were not started.%n", notExecuted.size());
   }
 
   System.out.printf("Total tasks completed: %d.%n", futures.stream().filter(Future::isDone).count());
}

Keluaran (berbeda dari lari ke lari):

10.000 tugas diajukan untuk dieksekusi.
9170 tugas tidak dimulai.
Total tugas selesai: 830 tugas.

Proses selesai dengan kode keluar 0