พูลเธรดอีกประเภทหนึ่งคือ "แคช" พูลเธรดดังกล่าวใช้กันทั่วไปเช่นเดียวกับเธรดแบบคงที่

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

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

ภายใต้ประทุน ตัวสร้าง ThreadPoolExecutorถูกเรียกโดยมีอาร์กิวเมนต์ต่อไปนี้:


public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, 
      new SynchronousQueue<Runnable>());
}

ค่าต่อไปนี้จะถูกส่งผ่านไปยังตัวสร้างเป็นอาร์กิวเมนต์:

พารามิเตอร์ ค่า
corePoolSize (จำนวนเธรดที่จะพร้อม (เริ่มต้น) เมื่อ บริการ ตัวดำเนินการเริ่มทำงาน) 0
maximumPoolSize (จำนวนเธรดสูงสุดที่ บริการ ตัวดำเนินการสามารถสร้างได้) จำนวนเต็ม MAX_VALUE
keepAliveTime (เวลาที่เธรดที่ว่างจะยังคงมีชีวิตอยู่ก่อนที่จะถูกทำลายหากจำนวนของเธรดมากกว่าcorePoolSize ) 60L
หน่วย (หน่วยของเวลา) เวลาหน่วยวินาที
workQueue (การดำเนินการตามคิว) ใหม่ SynchronousQueue <รันได้>()

และเราสามารถผ่านการใช้งานThreadFactoryในแบบเดียวกันได้

พูดคุยเกี่ยวกับ SynchronousQueue

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

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

พูลแคชเริ่มต้นด้วยเธรดศูนย์และสามารถขยายเป็นเธรดInteger.MAX_VALUE โดยพื้นฐานแล้ว ขนาดของพูลเธรดแคชจะถูกจำกัดโดยทรัพยากรระบบเท่านั้น

เพื่อรักษาทรัพยากรระบบ แคชเธรดพูลจะลบเธรดที่ไม่ได้ใช้งานเป็นเวลาหนึ่งนาที

มาดูกันว่าได้ผลจริงอย่างไร เราจะสร้างคลาสงานที่จำลองคำขอของผู้ใช้:


public class Task implements Runnable {
   int taskNumber;

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

   @Override
   public void run() {
       System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   }
}
    

ใน เมธอด หลักเราสร้างnewCachedThreadPoolแล้วเพิ่ม 3 งานสำหรับการดำเนินการ ที่นี่เราพิมพ์สถานะบริการของเรา(1) .

ต่อไป เราหยุดชั่วคราวเป็นเวลา 30 วินาที เริ่มงานอื่น และแสดงสถานะ(2) .

หลังจากนั้น เราหยุดเธรดหลักของเราชั่วคราวเป็นเวลา 70 วินาที พิมพ์สถานะ( 3)จากนั้นเพิ่มงาน 3 งานอีกครั้ง และพิมพ์สถานะอีกครั้ง(4)

ในสถานที่ที่เราแสดงสถานะทันทีหลังจากเพิ่มงาน อันดับแรก เราจะเพิ่มโหมดสลีป 1 วินาที สำหรับเอาต์พุตที่เป็นปัจจุบัน


ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }
 
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(1)
 
        TimeUnit.SECONDS.sleep(30);
 
        executorService.submit(new Task(3));
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(2)
 
        TimeUnit.SECONDS.sleep(70);
 
            System.out.println(executorService);	//(3)
 
        for (int i = 4; i < 7; i++) {
            executorService.submit(new Task(i));
        }
 
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(4)
        executorService.shutdown();

และนี่คือผลลัพธ์:

คำขอของผู้ใช้ที่ประมวลผล #0 บนเธรด pool-1-thread-1
คำขอของผู้ใช้ที่ประมวลผล #1 บนเธรด pool-1-thread-2
คำขอของผู้ใช้ที่ประมวลผล #2 บนเธรด pool-1-thread-3
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[กำลังทำงาน, ขนาดพูล = 3, เธรดที่ใช้งานอยู่ = 0, งานที่อยู่ในคิว = 0, งานที่เสร็จสมบูรณ์ = 3] คำขอของผู้ใช้ที่ประมวลผล #3 บนเธรด pool-1-thread-2 (
2
) java.util.concurrent ThreadPoolExecutor@f6f4d33[กำลังทำงาน ขนาดพูล = 3 เธรดที่ใช้งานอยู่ = 0 งานที่อยู่ในคิว = 0 งานที่เสร็จสมบูรณ์ = 4] (3) java.util.concurrent.ThreadPoolExecutor@f6f4d33[กำลังทำงาน
ขนาดพูล = 0 เธรดที่ใช้งานอยู่ = 0 , งานที่อยู่ในคิว = 0, งานที่เสร็จสมบูรณ์ = 4]
คำขอของผู้ใช้ที่ประมวลผล #4 บนเธรด pool-1-thread-4
คำขอของผู้ใช้ที่ประมวลผล #5 บนเธรด pool-1-thread-5
คำขอของผู้ใช้ที่ประมวลผล #6 บนเธรด pool-1-thread-4
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[กำลังทำงาน, ขนาดพูล = 2, เธรดที่ใช้งานอยู่ = 0, งานที่อยู่ในคิว = 0, งานที่ทำเสร็จแล้ว = 7]

มาดูกันทีละขั้นตอน:

ขั้นตอนที่ คำอธิบาย
1 (หลังจาก 3 ภารกิจเสร็จสิ้น) เราสร้าง 3 เธรด และ 3 งานถูกดำเนินการบนเธรดทั้งสามนี้
เมื่อสถานะแสดงขึ้น งานทั้ง 3 อย่างจะเสร็จสิ้น และเธรดพร้อมที่จะทำงานอื่นๆ
2 (หลังจากหยุดชั่วคราว 30 วินาทีและดำเนินการงานอื่น) หลังจากไม่มีการใช้งานเป็นเวลา 30 วินาที เธรดจะยังคงอยู่และกำลังรองาน
งานอื่นถูกเพิ่มและดำเนินการบนเธรดที่นำมาจากพูลของเธรดที่ใช้งานจริงที่เหลืออยู่
ไม่มีการเพิ่มเธรดใหม่ลงในกลุ่ม
3 (หลังจากหยุดชั่วคราว 70 วินาที) เธรดถูกลบออกจากพูล
ไม่มีเธรดที่พร้อมจะรับงาน
4 (หลังจากดำเนินการอีก 3 งาน) หลังจากได้รับงานเพิ่มเติม เธรดใหม่จะถูกสร้างขึ้น ครั้งนี้มีเพียง 2 เธรดที่จัดการเพื่อประมวลผล 3 งาน

ตอนนี้คุณคุ้นเคยกับตรรกะของบริการตัวดำเนินการประเภทอื่นแล้ว

ด้วยการเปรียบเทียบกับเมธอดอื่นๆ ของคลาสยูทิลิตี้Executors เมธอด newCachedThreadPoolยังมีเวอร์ชันโอเวอร์โหลดที่ใช้ ออบเจกต์ ThreadFactoryเป็นอาร์กิวเมนต์