เมธอด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 งานเพื่อดำเนินการ
ผู้ใช้ที่ประมวลผล #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
นี่คือเอาต์พุตคอนโซลที่เราได้รับ:
คำขอของผู้ใช้ที่ประมวลผล #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()จะส่งคืนตัวดำเนินการที่ไม่สามารถกำหนดค่าใหม่ในภายหลังเพื่อใช้เธรดเพิ่มเติมได้