Pertimbangkan program mudah:

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 output konsol yang kami harapkan:

selesai

Tetapi ini tidak diikuti oleh output yang biasanya kita lihat dalam IntelliJ IDEA:

Proses selesai dengan kod keluar 0

Kita biasa melihatnya apabila sesuatu program berakhir.

Mengapa ia berlaku?

Perihalan kaedah newFixedThreadPool() memberitahu kami bahawa utas yang dibuat menggunakan ExecutorService terus wujud sehingga ia dihentikan secara eksplisit. Ini bermakna kerana kami menyerahkan tugasan kepada ExecutorService , urutan telah dicipta untuk melaksanakannya dan urutan itu terus wujud walaupun selepas tugasan itu selesai.

Berhenti di ExecutorService

Akibatnya, kita perlu "menutup" (atau menghentikan) ExecutorService . Kita boleh melakukan ini dalam dua cara:

  1. void shutdown() — selepas kaedah ini dipanggil, ExecutorService berhenti menerima kerja baharu. Semua tugasan yang sebelum ini diserahkan kepada ExecutorService akan terus dijalankan.

    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() — Kaedah ini cuba menghentikan kerja yang sedang aktif. Tugasan yang masih menunggu giliran akan dibuang dan dikembalikan sebagai senarai 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);
    }

Pengeluaran:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[Belum selesai, tugasan = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[Tugas dibungkus = Test$$Lambda$16/0x0000000800b950470b950470b95070@java
. .FutureTask@cc34f4d[Belum selesai, tugasan = java.util.concurrent.Executors$RunnableAdapter@66a29884[Tugas dibungkus = Test$$Lambda$16/0x0000000800b95040@4769b07b]concurrent.6769b07b]concurrent.95040@util.Future.
tugasan = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Tugas dibungkus = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

Proses selesai dengan kod keluar 0

Output akan berbeza dari run ke run. Terdapat 2 jenis baris dalam output:

  • Nombor bermakna bahawa ExecutorService berjaya memproses tugasan yang sepadan, memaparkan nombor daripada senarai yang kami gunakan untuk membuat tugasan.

  • Keputusan memanggil kaedah toString() pada objek FutureTask . Objek ini ialah tugas yang telah diserahkan kepada ExecutorService tetapi tidak diproses.

Outputnya mempunyai satu lagi nuansa yang menarik. Dalam dunia yang ideal, kita mula-mula akan melihat semua nombor yang dipaparkan, diikuti dengan objek FutureTask . Tetapi isu penyegerakan mengacaukan baris dalam output.

Kaedah lain

ExecutorService mempunyai beberapa lagi kaedah yang berkaitan dengan menghentikannya:

  1. boolean awaitTermination(lama tamat masa, unit TimeUnit) — kaedah ini menyekat urutan yang memanggilnya. Blok itu tamat sebaik sahaja mana-mana satu daripada tiga peristiwa berikut berlaku:

    • selepas kaedah shutdown() dipanggil, semua kerja aktif dan semua tugas yang dijadualkan telah dilaksanakan;
    • tamat masa yang ditentukan oleh parameter kaedah telah berlalu;
    • benang yang dipanggil kaedah awaitTermination() ditamatkan.

    Kaedah mengembalikan benar jika ExecutorService dihentikan sebelum tamat masa berlalu, dan palsu jika tamat masa telah berlalu.

    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 benar jika kaedah shutdown() atau shutdownNow() telah dipanggil pada 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 benar jika kaedah shutdown() atau shutdownNow() telah dipanggil pada ExecutorService dan semua tugas telah 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 kod yang menggunakan kaedah 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());
}

Output (berbeza daripada run to run):

10,000 tugasan telah diserahkan untuk dilaksanakan.
9170 tugasan tidak dimulakan.
Jumlah tugasan selesai: 830 tugasan.

Proses selesai dengan kod keluar 0