FixedThreadPool vs CachedThreadPool: ความชั่วร้ายน้อยกว่าสองอย่าง


99

ฉันมีโปรแกรมที่สร้างเธรด (~ 5-150) ซึ่งทำงานได้หลายอย่าง ในขั้นต้นฉันใช้ a FixedThreadPoolเพราะคำถามที่คล้ายกันนี้แนะนำว่าเหมาะสำหรับงานที่มีชีวิตอยู่นานกว่าและด้วยความรู้ที่ จำกัด มากเกี่ยวกับมัลติเธรดฉันจึงคิดว่าอายุการใช้งานเฉลี่ยของเธรด (หลายนาที) " มีอายุยืนยาว "

อย่างไรก็ตามฉันเพิ่งเพิ่มความสามารถในการสร้างเธรดเพิ่มเติมและการทำเช่นนั้นทำให้ฉันอยู่เหนือขีด จำกัด เธรดที่ฉันตั้งไว้ ในกรณีนี้จะดีกว่าไหมหากเดาและเพิ่มจำนวนเธรดที่ฉันอนุญาตหรือเปลี่ยนเป็นไฟล์CachedThreadPoolเธรดเพื่อให้ฉันไม่มีเธรดที่สูญเปล่า

ลองใช้ทั้งคู่ในเบื้องต้นดูเหมือนจะไม่มีความแตกต่างดังนั้นฉันจึงมีแนวโน้มที่จะไปด้วยวิธีCachedThreadPoolเดียวที่จะหลีกเลี่ยงของเสีย อย่างไรก็ตามช่วงชีวิตของเธรดหมายความว่าฉันควรเลือกFixedThreadPoolและจัดการกับเธรดที่ไม่ได้ใช้แทนหรือไม่? คำถามนี้ทำให้ดูเหมือนว่าเธรดพิเศษเหล่านั้นจะไม่สูญเปล่า แต่ฉันขอขอบคุณสำหรับคำชี้แจง

คำตอบ:


113

CachedThreadPool เป็นสิ่งที่คุณควรใช้สำหรับสถานการณ์ของคุณเนื่องจากไม่มีผลเสียใด ๆ ในการใช้เธรดที่รันเป็นเวลานาน ข้อคิดเห็นในเอกสาร java เกี่ยวกับ CachedThreadPools เหมาะสำหรับงานสั้น ๆ เพียงแนะนำว่าเหมาะสมเป็นพิเศษสำหรับกรณีดังกล่าวไม่ใช่ว่าไม่สามารถหรือไม่ควรใช้สำหรับงานที่เกี่ยวข้องกับงานที่รันเป็นเวลานาน

หากต้องการอธิบายเพิ่มเติมExecutors.newCachedThreadPoolและExecutors.newFixedThreadPoolทั้งสองได้รับการสนับสนุนโดยการใช้เธรดพูลเดียวกัน (อย่างน้อยใน JDK แบบเปิด) เพียงแค่มีพารามิเตอร์ที่แตกต่างกัน ความแตกต่างเพียงแค่เธรดต่ำสุดสูงสุดเวลาฆ่าเธรดและประเภทคิว

public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
 }

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

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

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


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

1
ทำไมไม่เพียงสร้างสิ่งที่ThreadPoolExecutorเหมือนขอบเขตThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar

48

ทั้งสองอย่างFixedThreadPoolและCachedThreadPoolเป็นความชั่วร้ายในแอพพลิเคชั่นที่โหลดสูง

CachedThreadPool เป็นอันตรายมากกว่า FixedThreadPool

หากแอปพลิเคชันของคุณโหลดสูงและต้องการเวลาแฝงต่ำควรกำจัดทั้งสองตัวเลือกเนื่องจากข้อเสียด้านล่าง

  1. ลักษณะของคิวงานที่ไม่ถูกผูกไว้: อาจทำให้หน่วยความจำไม่เพียงพอหรือมีเวลาแฝงสูง
  2. เธรดที่รันเป็นเวลานานจะทำให้CachedThreadPoolควบคุมการสร้างเธรดไม่ได้

เนื่องจากคุณรู้ว่าทั้งสองอย่างเป็นความชั่วร้ายความชั่วร้ายที่น้อยกว่าไม่ได้ทำความดีใด ๆ ชอบThreadPoolExecutorซึ่งให้การควบคุมแบบละเอียดสำหรับพารามิเตอร์จำนวนมาก

  1. ตั้งค่าคิวงานเป็นคิวที่มีขอบเขตเพื่อให้สามารถควบคุมได้ดีขึ้น
  2. มี RejectionHandler ที่ถูกต้อง - ตัวจัดการ RejectionHandler หรือ Default ของคุณเองที่จัดทำโดย JDK
  3. หากคุณมีสิ่งที่ต้องทำก่อน / หลังเสร็จสิ้นภารกิจให้ลบล้างbeforeExecute(Thread, Runnable)และafterExecute(Runnable, Throwable)
  4. แทนที่ThreadFactoryหากจำเป็นต้องปรับแต่งเธรด
  5. ควบคุมขนาดพูลเธรดแบบไดนามิกในเวลารัน (คำถาม SE ที่เกี่ยวข้อง: Dynamic Thread Pool )

จะเกิดอะไรขึ้นถ้าใครบางคนตัดสินใจใช้ commonPool?
Crosk Cool

1
@Ravindra - คุณได้อธิบายข้อเสียของทั้ง CachedThreadPool และ FixedThreadPool อย่างสวยงาม นี่แสดงว่าคุณมีความเข้าใจอย่างลึกซึ้งเกี่ยวกับแพ็กเกจการทำงานพร้อมกัน
Ayaskant

5

ดังนั้นฉันจึงมีโปรแกรมที่สร้างเธรด (~ 5-150) ซึ่งทำงานได้หลายอย่าง

คุณแน่ใจหรือไม่ว่าคุณเข้าใจวิธีการประมวลผลเธรดโดยระบบปฏิบัติการและฮาร์ดแวร์ที่คุณเลือก? Java แมปเธรดกับเธรด OS อย่างไรแมปเธรดกับเธรด CPU ฯลฯ อย่างไร ฉันถามเพราะการสร้าง 150 เธรดภายใน ONE JRE เหมาะสมก็ต่อเมื่อคุณมีคอร์ / เธรด CPU ขนาดใหญ่อยู่ข้างใต้ซึ่งส่วนใหญ่ไม่เป็นเช่นนั้น ขึ้นอยู่กับ OS และ RAM ที่ใช้การสร้างมากกว่า n เธรดอาจส่งผลให้ JRE ของคุณถูกยกเลิกเนื่องจากข้อผิดพลาด OOM ดังนั้นคุณควรแยกความแตกต่างระหว่างเธรดและงานที่ต้องทำโดยเธรดเหล่านั้นจำนวนงานที่คุณสามารถประมวลผลได้ ฯลฯ

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

และปัญหานั้นจะไม่เกิดขึ้นกับ FixedThreadPool ที่สร้างขึ้นด้วยเธรดเช่น 2 + n โดยที่ n มีค่าต่ำพอสมควรเนื่องจากทรัพยากรฮาร์ดแวร์และระบบปฏิบัติการนั้นใช้โดยมีค่าใช้จ่ายน้อยกว่ามากสำหรับการจัดการเธรดซึ่งไม่สามารถทำงานได้


บางครั้งไม่มีทางเลือกที่ดีกว่าคุณสามารถมีแกน CPU เพียง 1 คอร์ แต่ถ้าคุณกำลังเรียกใช้เซิร์ฟเวอร์ที่คำขอของผู้ใช้ทุกคนจะทริกเกอร์เธรดเพื่อประมวลผลคำขอจะไม่มีทางเลือกอื่นที่เหมาะสมโดยเฉพาะหากคุณวางแผน เพื่อขยายขนาดเซิร์ฟเวอร์เมื่อคุณขยายฐานผู้ใช้
Michel Feinstein

@mFeinstein เราจะไม่มีทางเลือกได้อย่างไรหากอยู่ในตำแหน่งที่จะเลือกการใช้งานเธรดพูล ในตัวอย่างของคุณที่มีคอร์ CPU 1 คอร์เท่านั้นที่จะสร้างเธรดมากขึ้นก็ไม่สมเหตุสมผลเลยมันเหมาะอย่างยิ่งกับตัวอย่างของฉันโดยใช้ FixedThreadPool ที่ปรับขนาดได้ง่ายเช่นกันก่อนอื่นด้วยเธรดผู้ปฏิบัติงานบนหรือสองเธรดต่อมาด้วย 10 หรือ 15 ขึ้นอยู่กับจำนวนคอร์
Thorsten Schöning

2
การใช้งานเว็บเซิร์ฟเวอร์ส่วนใหญ่จะสร้างเธรดใหม่หนึ่งเธรดสำหรับคำขอ HTTP ใหม่แต่ละครั้ง ... พวกเขาไม่สนใจว่าเครื่องมีคอร์จริงกี่คอร์ทำให้การใช้งานง่ายขึ้นและปรับขนาดได้ง่ายขึ้น สิ่งนี้ใช้ได้กับการออกแบบอื่น ๆ อีกมากมายที่คุณต้องการโค้ดเพียงครั้งเดียวและปรับใช้และไม่ต้องคอมไพล์ใหม่และปรับใช้ใหม่หากคุณเปลี่ยนเครื่องซึ่งอาจเป็นอินสแตนซ์ระบบคลาวด์
Michel Feinstein

@mFeinstein เว็บเซิร์ฟเวอร์ส่วนใหญ่ใช้เธรดพูลสำหรับการร้องขอของตนเองเพียงเพราะการวางไข่เธรดที่ไม่สามารถรันไม่สมเหตุสมผลหรือพวกเขาใช้เหตุการณ์ลูปสำหรับการเชื่อมต่อและประมวลผลคำขอในพูลในภายหลังหรือเช่นนั้น นอกจากนี้คุณยังขาดประเด็นซึ่งก็คือคำถามเกี่ยวกับความสามารถในการเลือกพูลเธรดที่ถูกต้องและเธรดการวางไข่ซึ่งไม่สามารถรันได้ แต่ก็ยังไม่สมเหตุสมผล FixedthreadPool กำหนดค่าให้เป็นจำนวนเธรดที่เหมาะสมต่อเครื่องโดยขึ้นอยู่กับสเกลแกนที่ดี
Thorsten Schöning

3
@ ThorstenSchöningการมี 50 เธรดที่ผูกกับ CPU บนเครื่อง 2 คอร์นั้นไม่เป็นประโยชน์ การมี 50 IO-bound thread บนเครื่อง 2 คอร์จะมีประโยชน์มาก
Paul Draper
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.