เมธอดnewFixedThreadPoolของ คลาส Executorsจะสร้างexecutorServiceด้วยจำนวนเธรดที่แน่นอน ซึ่งแตกต่างจาก เมธอด newSingleThreadExecutorเราระบุจำนวนเธรดที่เราต้องการในพูล ภายใต้ประทุน รหัสต่อไปนี้เรียกว่า:


new ThreadPoolExecutor(nThreads, nThreads,
                                      	0L, TimeUnit.MILLISECONDS,
                                      	new LinkedBlockingQueue());

พารามิเตอร์corePoolSize (จำนวนของเธรดที่จะพร้อม (เริ่มต้น) เมื่อ บริการ ตัวดำเนินการเริ่มต้น) และmaximumPoolSize (จำนวนเธรดสูงสุดที่ บริการ ตัวดำเนินการสามารถสร้างได้) ได้รับค่าเดียวกัน — จำนวนของเธรดที่ส่งผ่านไปยังnewFixedThreadPool(nThreads ) . และเราสามารถผ่านการใช้งานThreadFactoryในแบบเดียวกันได้

เรามาดูกันว่าทำไมเราถึงต้องการExecutorService

นี่คือตรรกะของExecutorServiceที่มีจำนวนเธรด (n) คงที่:

  • เธรดสูงสุด n เธรดจะถูกใช้งานสำหรับการประมวลผลงาน
  • หากมีการส่งมากกว่า n งาน งานเหล่านั้นจะถูกพักไว้ในคิวจนกว่าเธรดจะว่าง
  • หากหนึ่งในเธรดล้มเหลวและยุติลง เธรดใหม่จะถูกสร้างขึ้นเพื่อแทนที่
  • เธรดใดๆ ในพูลจะทำงานจนกว่าพูลจะปิดตัวลง

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

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

ฉันขอแนะนำให้ทำความเข้าใจในทางปฏิบัติว่าExecutorServiceทำงานอย่างไรกับจำนวนเธรดที่แน่นอน มาสร้างคลาสที่ใช้Runnableกัน เถอะ ออบเจ็ กต์ของคลาสนี้แสดงถึงงานของเราสำหรับExecutorService


public class Task implements Runnable {
    int taskNumber;
 
    public Task(int taskNumber) {
        this.taskNumber = taskNumber;
    }
 
    @Override
    public void run() {
try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
    }
}
    

ใน เมธอด run()เราบล็อกเธรดเป็นเวลา 2 วินาที จำลองปริมาณงานบางส่วน จากนั้นแสดงหมายเลขของงานปัจจุบันและชื่อของเธรดที่เรียกใช้งาน


ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        for (int i = 0; i < 30; i++) {
            executorService.execute(new Task(i));
        }
        
        executorService.shutdown();
    

ในการเริ่มต้น ใน วิธีการ หลักเราสร้างExecutorServiceและส่ง 30 งานเพื่อดำเนินการ

คำขอของผู้ใช้ที่ประมวลผล #1 บนเธรด pool-1-thread-2 คำขอของ
ผู้ใช้ที่ประมวลผล #0 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #2 บนเธรด pool-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #5 บน pool- เธรด 1-thread-3
ประมวลผลคำขอของผู้ใช้ #3 บนเธรด pool-1-thread-2
ประมวลผลคำขอของผู้ใช้ #4 บนเธรด pool-1-thread-1
ประมวลผลคำขอของผู้ใช้ #8 บนเธรด pool-1-1-thread-1 ผู้
ใช้ที่ประมวลผล คำขอ #6 บนเธรด pool-1-thread-3 คำขอ
ของผู้ใช้ที่ประมวลผล #7 บนเธรด pool-1-thread-2 คำขอของ
ผู้ใช้ที่ประมวลผล #10 บนเธรด pool-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #9 บน pool-1- เธรด 1 เธรด
ประมวลผลคำขอของผู้ใช้ #11 บนเธรด pool-1-thread-2
ประมวลผลคำขอของผู้ใช้ #12 บนเธรด pool-1-thread-3
คำขอของผู้ใช้ที่ประมวลผล #14 บนเธรด pool-1-thread-2 คำขอของ
ผู้ใช้ที่ประมวลผล #13 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #15 บนเธรด pool-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #16 บน pool- 1 เธรด 2 เธรด
คำขอของผู้ใช้ที่ประมวลผล #17 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #18 บนเธรด pool-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #19 บนเธรด pool-1-1-thread-2
ผู้ใช้ที่ประมวลผล คำขอ #20 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #21 บนเธรด pool-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #22 บนเธรด pool-1-1-thread-2 คำขอของ
ผู้ใช้ที่ประมวลผล #23 บน pool-1- เธรด 1 เธรด
ประมวลผลคำขอของผู้ใช้ #25 บนเธรด pool-1-thread-2
ประมวลผลคำขอของผู้ใช้ #24 บนเธรด pool-1-thread-3
คำขอของผู้ใช้ที่ประมวลผล #26 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #27 บนเธรด pool-1-thread-2
คำขอของผู้ใช้ที่ประมวลผล #28 บนเธรด pool-1-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #29 บน pool- 1 เธรด 1 เธรด

เอาต์พุตของคอนโซลแสดงให้เราเห็นว่างานถูกดำเนินการอย่างไรบนเธรดต่างๆ เมื่อมีการเผยแพร่โดยงานก่อนหน้า

ตอนนี้เราจะเพิ่มจำนวนงานเป็น 100 งาน และหลังจากส่งงานครบ 100 งาน เราจะเรียกเมธอดwaitTermination (11, SECONDS) เราส่งหน่วยตัวเลขและเวลาเป็นอาร์กิวเมนต์ วิธีนี้จะบล็อกเธรดหลักเป็นเวลา 11 วินาที จากนั้นเราจะเรียกshutdownNow()เพื่อบังคับให้ExecutorServiceปิดตัวลงโดยไม่รอให้งานทั้งหมดเสร็จสิ้น


ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Task(i));
        }
 
        executorService.awaitTermination(11, SECONDS);
 
        executorService.shutdownNow();
        System.out.println(executorService);
    

ในตอน ท้ายเราจะแสดงข้อมูลเกี่ยวกับสถานะของexecutorService

นี่คือเอาต์พุตคอนโซลที่เราได้รับ:

คำขอของผู้ใช้ที่ประมวลผล #0 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #2 บนเธรด pool-1-thread-3
คำขอของผู้ใช้ที่ประมวลผล #1 บนเธรด pool-1-1-thread-2 คำขอของ
ผู้ใช้ที่ประมวลผล #4 บน pool- เธรด 1-thread-3
ประมวลผลคำขอของผู้ใช้ #5 บนเธรด pool-1-thread-2
ประมวลผลคำขอของผู้ใช้ #3 บนเธรด pool-1-thread-1
ประมวลผลคำขอของผู้ใช้ #6 บนเธรด pool-1-thread-3 ผู้
ใช้ที่ประมวลผล คำขอ #7 บนเธรด pool-1-thread-2 คำขอ
ของผู้ใช้ที่ประมวลผล #8 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #9 บนเธรด pool-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #11 บน pool-1- เธรด 1 เธรด
ประมวลผลคำขอของผู้ใช้ #10 บนเธรด pool-1-thread-2
ประมวลผลคำขอของผู้ใช้ #13 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #14 บนเธรด pool-1-thread-2
คำขอของผู้ใช้ที่ประมวลผล #12 บนเธรด pool-1-thread-3
java.util.concurrent.ThreadPoolExecutor@452b3a41[การปิดระบบ ขนาดพูล = 3 เธรดที่ใช้งานอยู่ = 3 , งานที่อยู่ในคิว = 0, งานที่เสร็จสมบูรณ์ = 15]
คำขอของผู้ใช้ที่ประมวลผล #17 บนเธรด pool-1-thread-3 คำขอของ
ผู้ใช้ที่ประมวลผล #15 บนเธรด pool-1-thread-1 คำขอของ
ผู้ใช้ที่ประมวลผล #16 บน pool-1-thread -2 เธรด

ตามด้วย 3 InterruptedExceptionsโยนโดย วิธี สลีปจาก 3 งานที่ทำงานอยู่

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

ดังนั้นเมื่อใช้ExecutorServiceด้วยจำนวนเธรดที่แน่นอนในพูล อย่าลืมจำวิธีการทำงาน ประเภทนี้เหมาะสำหรับงานที่มีโหลดคงที่

นี่เป็นอีกคำถามที่น่าสนใจ: หากคุณต้องการใช้ executor สำหรับเธรดเดียว คุณควรเรียกใช้เมธอดใด newSingleThreadExecutor()หรือnewFixedThreadPool(1) ?

ผู้ดำเนินการทั้งสองจะมีพฤติกรรมที่เท่าเทียมกัน ข้อแตกต่างเพียงอย่างเดียวคือ เมธอด newSingleThreadExecutor()จะส่งคืนตัวดำเนินการที่ไม่สามารถกำหนดค่าใหม่ในภายหลังเพื่อใช้เธรดเพิ่มเติมได้