พิจารณาโปรแกรมง่ายๆ:


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"));
}

การรันโปรแกรมสร้างเอาต์พุตคอนโซลที่เราคาดหวัง:

เสร็จแล้ว

แต่ไม่ได้ตามด้วยผลลัพธ์ที่เรามักจะเห็นใน IntelliJ IDEA:

กระบวนการเสร็จสิ้นด้วยรหัสออก 0

เรามักจะเห็นว่าเมื่อโปรแกรมสิ้นสุดลง

ทำไมถึงเกิดขึ้น?

คำอธิบายของ เมธอด newFixedThreadPool()บอกเราว่าเธรดที่สร้างโดยใช้ExecutorServiceยังคงอยู่จนกว่าจะหยุดทำงานอย่างชัดเจน นั่นหมายถึงเพราะเราส่งต่องานไปยังExecutorServiceเธรดจึงถูกสร้างขึ้นเพื่อเรียกใช้งาน และเธรดนั้นจะยังคงอยู่แม้ว่างานจะเสร็จสิ้นแล้วก็ตาม

หยุดที่ ExecutorService

เป็นผลให้เราต้อง "ปิด" (หรือหยุด) ExecutorService เราสามารถทำได้สองวิธี:

  1. void shutdown() — หลังจากเรียกใช้เมธอดนี้ ExecutorService จะหยุดรับงานใหม่ งานทั้งหมดที่ส่งไปยังExecutorService ก่อนหน้านี้ จะยังคงทำงานต่อไป

    
    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() — เมธอดนี้พยายามหยุดงานที่กำลังทำงานอยู่ งานที่ยังคงรอ ถึงตาของพวกเขาจะถูกยกเลิกและส่งคืนเป็นรายการ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);
    }
    

เอาท์พุต:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[ยังไม่เสร็จ งาน = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[งานที่รวมไว้ = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[ยังไม่เสร็จสมบูรณ์ งาน = java.util.concurrent.Executors$RunnableAdapter@66a29884[งานที่รวมไว้ = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[ยังไม่เสร็จ งาน = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[งานที่รวมไว้ = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

ดำเนินการเสร็จสิ้นด้วยรหัสออก 0

ผลลัพธ์จะแตกต่างกันไปในแต่ละรัน เอาต์พุตมี 2 บรรทัด:

  • ตัวเลขหมายความว่าExecutorServiceจัดการเพื่อประมวลผลงานที่เกี่ยวข้อง โดยแสดงตัวเลขจากรายการที่เราใช้สร้างงาน

  • ผลลัพธ์ของการเรียก ใช้เมธอด toString()บนวัตถุFutureTask อ็อบเจ็กต์เหล่านี้เป็นงานที่ส่งไปยัง ExecutorService แต่ไม่ได้รับการประมวลผล

ผลลัพธ์มีความแตกต่างที่น่าสนใจอีกอย่างหนึ่ง ในโลกอุดมคติ อันดับแรกเราจะเห็นตัวเลขที่แสดงทั้งหมด ตามด้วยวัตถุFutureTask แต่ปัญหาการซิงโครไนซ์ทำให้บรรทัดในเอาต์พุตสับสน

วิธีการอื่นๆ

ExecutorServiceมีหลายวิธีที่เกี่ยวข้องกับการหยุด:

  1. บูลีน waitTermination (การหมดเวลานาน, หน่วย TimeUnit) — เมธอดนี้บล็อกเธรดที่เรียกใช้ การบล็อกจะสิ้นสุดลงทันทีที่มีเหตุการณ์ใดเหตุการณ์หนึ่งในสามเหตุการณ์ต่อไปนี้เกิดขึ้น:

    • หลังจากเรียกเมธอดshutdown() งานที่ใช้งานอยู่และงานตามกำหนดเวลาทั้งหมดจะถูกดำเนินการ
    • การหมดเวลาที่กำหนดโดยพารามิเตอร์วิธีการผ่านไปแล้ว
    • เธรดที่เรียกใช้ เมธอด waitTermination()ถูกยกเลิก

    เมธอดจะคืนค่าจริงหาก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"));
    	service.shutdown();
    	System.out.println(service.awaitTermination(1, TimeUnit.MICROSECONDS));
    }
    
  2. บูลีน isShutdown() — คืนค่าจริงหากเมธอดshutdown ()หรือshutdownNow()ถูกเรียกใช้บน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. บูลีน isTerminated() — คืนค่าจริงหาก เมธอด shutdown()หรือshutdownNow()ถูกเรียกใช้บนExecutorServiceและงานทั้งหมดเสร็จสิ้น

    
    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());
    }
    

ตัวอย่างโค้ดที่ใช้วิธีการเหล่านี้:


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());
}

เอาท์พุต (แตกต่างจากรันทูรัน):

ส่งงาน 10,000 งานเพื่อดำเนินการ
ไม่ได้เริ่มงาน 9170 งาน
งานทั้งหมดที่เสร็จสมบูรณ์: 830 งาน

กระบวนการเสร็จสิ้นด้วยรหัสออก 0