เหตุใดคุณจึงต้องการ ExecutorService สำหรับ 1 เธรด

คุณสามารถใช้ เมธอด Executors.newSingleThreadExecutorเพื่อสร้างExecutorServiceด้วยพูลที่มีเธรดเดียว ตรรกะของพูลเป็นดังนี้:

  • บริการดำเนินการครั้งละหนึ่งงานเท่านั้น
  • หากเราส่งงาน N งานเพื่อดำเนินการ งาน N งานทั้งหมดจะถูกดำเนินการทีละงานโดยเธรดเดียว
  • หากเธรดถูกขัดจังหวะ เธรดใหม่จะถูกสร้างขึ้นเพื่อดำเนินการงานที่เหลืออยู่

ลองนึกภาพสถานการณ์ที่โปรแกรมของเราต้องการฟังก์ชันต่อไปนี้:

เราจำเป็นต้องดำเนินการตามคำขอของผู้ใช้ภายใน 30 วินาที แต่ไม่เกินหนึ่งคำขอต่อหน่วยเวลา

เราสร้างคลาสงานสำหรับการประมวลผลคำขอของผู้ใช้:


class Task implements Runnable {
   private final int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       try {
           Thread.sleep(1000);
       } catch (InterruptedException ignored) {
       }
       System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
   }
}
    

คลาสจำลองพฤติกรรมของการประมวลผลคำขอที่เข้ามาและแสดงหมายเลขของมัน

ต่อไป ใน เมธอด หลักเราสร้างExecutorServiceสำหรับ 1 เธรด ซึ่งเราจะใช้เพื่อประมวลผลคำขอที่เข้ามาตามลำดับ เนื่องจากเงื่อนไขของงานกำหนดไว้ว่า "ภายใน 30 วินาที" เราจึงเพิ่มการรอ 30 วินาที จากนั้นบังคับให้หยุดการทำงานของExecutorService


public static void main(String[] args) throws InterruptedException {
   ExecutorService executorService = Executors.newSingleThreadExecutor();

   for (int i = 0; i < 1_000; i++) {
       executorService.execute(new Task(i));
   }
   executorService.awaitTermination(30, TimeUnit.SECONDS);
   executorService.shutdownNow();
}
    

หลังจากเริ่มโปรแกรม คอนโซลจะแสดงข้อความเกี่ยวกับการประมวลผลคำขอ:

คำขอที่ประมวลผล #0 บนเธรด id=16
คำขอที่ประมวลผล #1 บนเธรด id=16
คำขอที่ประมวลผล #2 บนเธรด id=16

คำขอที่ประมวลผล #29 บนเธรด id=16

หลังจากประมวลผลคำขอเป็นเวลา 30 วินาทีexecutorServiceจะเรียกเมธอดshutdownNow()ซึ่งจะหยุดงานปัจจุบัน (งานที่กำลังดำเนินการ) และยกเลิกงานที่ค้างอยู่ทั้งหมด หลังจากนั้นโปรแกรมก็จบลงด้วยดี

แต่ทุกอย่างไม่ได้สมบูรณ์แบบเสมอไป เนื่องจากโปรแกรมของเราอาจมีสถานการณ์ที่งานอย่างใดอย่างหนึ่งที่หยิบขึ้นมาจากเธรดเดียวในกลุ่มของเราทำงานไม่ถูกต้องและแม้แต่ยุติเธรดของเรา เราสามารถจำลองสถานการณ์นี้เพื่อดูว่าexecutorServiceทำงานอย่างไรกับเธรดเดียวในกรณีนี้

ในการดำเนินการนี้ ในขณะที่งานหนึ่งกำลังดำเนินการอยู่ เราจะยุติเธรดของเราโดยใช้ เมธอดThread.currentThread().stop()ที่ไม่ปลอดภัยและล้าสมัย เรากำลังทำสิ่งนี้โดยเจตนาเพื่อจำลองสถานการณ์ที่งานอย่างใดอย่างหนึ่งยุติเธรด

เราจะเปลี่ยน วิธี การทำงานใน คลาส งาน :


@Override
public void run() {
   try {
       Thread.sleep(1000);
   } catch (InterruptedException ignored) {
   }

   if (taskNumber == 5) {
       Thread.currentThread().stop();
   }

   System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
    

เราจะขัดจังหวะภารกิจ #5

มาดูกันว่าผลลัพธ์จะเป็นอย่างไรเมื่อเธรดถูกขัดจังหวะเมื่อสิ้นสุดงาน #5:

คำขอที่ประมวลผล #0 บนเธรด id=16
คำขอที่ประมวลผล #1 บนเธรด id=16
คำขอที่ประมวลผล #2 บนเธรด id=16
คำขอที่ประมวลผล #3 บนเธรด id=16
คำขอที่ประมวลผล #4 บนเธรด id=16
คำขอที่ประมวลผล #6 บน เธรด id=17
คำขอที่ประมวลผล #7 บนเธรด id=17

คำขอที่ประมวลผล #29 บนเธรด id=17

เราเห็นว่าหลังจากเธรดถูกขัดจังหวะเมื่อสิ้นสุดภารกิจที่ 5 งานจะเริ่มดำเนินการในเธรดที่มีตัวระบุเป็น 17 แม้ว่าก่อนหน้านี้จะถูกดำเนินการบนเธรดที่มีตัวระบุเป็น 16 และเนื่องจากกลุ่มของเรามี เธรดเดียว สิ่งนี้สามารถหมายถึงสิ่งเดียวเท่านั้น: executorServiceแทนที่เธรดที่หยุดทำงานด้วยเธรดใหม่และดำเนินการงานต่อไป

ดังนั้น เราควรใช้newSingleThreadExecutorกับ single-thread pool เมื่อเราต้องการประมวลผลงานตามลำดับและทีละงานเท่านั้น และเราต้องการประมวลผลงานต่อจากคิวโดยไม่คำนึงว่างานก่อนหน้าจะเสร็จสิ้นหรือไม่ (เช่น กรณีที่งานหนึ่ง งานของเราฆ่าเธรด)

โรงงานด้าย

เมื่อพูดถึงการสร้างและการสร้างเธรดใหม่ เราอดไม่ได้ที่จะพูดถึงโรงงานด้าย.

โรงงานด้ายเป็นวัตถุที่สร้างเธรดใหม่ตามต้องการ

เราสามารถสร้างโรงงานสร้างเธรดของเราเองและส่งอินสแตนซ์ของมันไปยังเมธอดExecutors.newSingleThreadExecutor (ThreadFactory threadFactory)


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
เราลบล้างวิธีการสร้างเธรดใหม่ โดยส่งชื่อเธรดไปยังคอนสตรัคเตอร์

ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "MyThread");
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        });
                    
เราเปลี่ยนชื่อและลำดับความสำคัญของเธรดที่สร้างขึ้น

ดังนั้นเราจึงเห็นว่าเรามี เมธอดExecutors.newSingleThreadExecutorที่โอเวอร์โหลด 2 เมธอด อันหนึ่งไม่มีพารามิเตอร์ และอันที่สองมีพารามิเตอร์ThreadFactory

เมื่อใช้ThreadFactoryคุณสามารถกำหนดค่าเธรดที่สร้างขึ้นได้ตามต้องการ ตัวอย่างเช่น โดยการตั้งค่าลำดับความสำคัญ ใช้คลาสย่อยของเธรด การเพิ่ม UncaughtExceptionHandler ให้กับเธรด และอื่นๆ