newCachedThreadPool()
กับ newFixedThreadPool()
เมื่อใดที่ฉันควรใช้รายการใดรายการหนึ่ง กลยุทธ์ใดดีกว่าในแง่ของการใช้ทรัพยากร
newCachedThreadPool()
กับ newFixedThreadPool()
เมื่อใดที่ฉันควรใช้รายการใดรายการหนึ่ง กลยุทธ์ใดดีกว่าในแง่ของการใช้ทรัพยากร
คำตอบ:
ฉันคิดว่าเอกสารอธิบายความแตกต่างและการใช้งานของทั้งสองฟังก์ชันได้ค่อนข้างดี:
สร้างเธรดพูลที่ใช้เธรดจำนวนคงที่ซึ่งใช้งานคิวที่ไม่ถูก จำกัด ที่แบ่งใช้ ณ จุดใด ๆ เธรดส่วนใหญ่จะเป็นงานการประมวลผลที่ใช้งานอยู่ หากมีการส่งงานเพิ่มเติมเมื่อเธรดทั้งหมดแอ็คทีฟงานเหล่านั้นจะรออยู่ในคิวจนกว่าเธรดจะพร้อมใช้งาน หากเธรดใด ๆ ถูกยกเลิกเนื่องจากความล้มเหลวในระหว่างการดำเนินการก่อนที่จะปิดระบบเธรดใหม่จะเข้าแทนที่หากจำเป็นเพื่อดำเนินงานที่ตามมา เธรดในพูลจะมีอยู่จนกว่าจะปิดอย่างชัดเจน
สร้างเธรดพูลที่สร้างเธรดใหม่ตามต้องการ แต่จะใช้เธรดที่สร้างขึ้นก่อนหน้านี้ใหม่เมื่อพร้อมใช้งาน กลุ่มเหล่านี้มักจะปรับปรุงประสิทธิภาพของโปรแกรมที่เรียกใช้งานแบบอะซิงโครนัสระยะสั้นจำนวนมาก การเรียกใช้เพื่อดำเนินการจะนำเธรดที่สร้างก่อนหน้านี้มาใช้อีกครั้งหากมี ถ้าไม่มีเธรดที่มีอยู่เธรดใหม่จะถูกสร้างและเพิ่มลงในพูล เธรดที่ไม่ได้ใช้งานเป็นเวลาหกสิบวินาทีจะถูกยกเลิกและลบออกจากแคช ดังนั้นกลุ่มที่ยังคงว่างนานพอจะไม่ใช้ทรัพยากรใด ๆ โปรดทราบว่ากลุ่มที่มีคุณสมบัติคล้ายกัน แต่อาจมีรายละเอียดที่แตกต่างกัน (ตัวอย่างเช่นพารามิเตอร์การหมดเวลา) โดยใช้ตัวสร้าง ThreadPoolExecutor
ในแง่ของทรัพยากรnewFixedThreadPool
จะทำให้เธรดทั้งหมดทำงานจนกว่าจะถูกยกเลิกอย่างชัดเจน ในnewCachedThreadPool
เธรดที่ไม่ได้ใช้งานเป็นเวลาหกสิบวินาทีจะถูกยกเลิกและลบออกจากแคช
เมื่อพิจารณาถึงสิ่งนี้การใช้ทรัพยากรจะขึ้นอยู่กับสถานการณ์เป็นอย่างมาก FixedThreadPool
ตัวอย่างเช่นถ้าคุณมีจำนวนมากของงานที่ทำงานนานฉันจะแนะนำ สำหรับCachedThreadPool
เอกสารนั้นกล่าวว่า "กลุ่มเหล่านี้มักจะปรับปรุงประสิทธิภาพของโปรแกรมที่ดำเนินงานแบบอะซิงโครนัสอายุสั้นจำนวนมาก"
newCachedThreadPool
อาจทำให้เกิดปัญหาร้ายแรงบางอย่างเนื่องจากคุณปล่อยให้การควบคุมทั้งหมดthread pool
และเมื่อบริการกำลังทำงานร่วมกับผู้อื่นในโฮสต์เดียวกันซึ่งอาจทำให้เกิดปัญหาอื่น ๆ เนื่องจาก CPU รอเป็นเวลานาน ดังนั้นฉันคิดว่าnewFixedThreadPool
จะปลอดภัยมากขึ้นในสถานการณ์ประเภทนี้ นอกจากนี้โพสต์นี้จะอธิบายความแตกต่างที่โดดเด่นที่สุดระหว่างพวกเขา
เพียงเพื่อทำคำตอบอื่น ๆ ให้สมบูรณ์ฉันต้องการเสนอราคา Java รุ่นที่ 2 โดย Joshua Bloch ตอนที่ 10 ข้อ 68:
"การเลือกบริการตัวจัดการสำหรับแอปพลิเคชันเฉพาะอาจเป็นเรื่องยากหากคุณกำลังเขียน โปรแกรมขนาดเล็กหรือเซิร์ฟเวอร์ที่โหลดเบา ๆโดยใช้Executors.New- CachedThreadPool นั้นเป็นตัวเลือกที่ดีเนื่องจากไม่ต้องการการกำหนดค่าและโดยทั่วไปแล้ว" สิ่งที่ถูกต้อง." แต่กลุ่มเธรดที่แคชไว้ไม่ใช่ตัวเลือกที่ดีสำหรับเซิร์ฟเวอร์ที่ใช้งานจริงจำนวนมาก !
ในสระว่ายน้ำด้ายที่แคช , งานส่งไม่ได้เข้าคิวแต่ส่งออกไปด้ายสำหรับการดำเนินการทันที ถ้าไม่มีเธรดจะมีการสร้างเธรดใหม่หากไม่มีหัวข้อที่มีอยู่ใหม่จะถูกสร้างขึ้นหากเซิร์ฟเวอร์ถูกโหลดอย่างหนักจนซีพียูทั้งหมดถูกใช้อย่างเต็มที่และมีงานมาถึงจำนวนมากเธรดจำนวนมากจะถูกสร้างขึ้นซึ่งจะทำให้เรื่องแย่ลงเท่านั้น
ดังนั้นในเซิร์ฟเวอร์ที่ใช้งานจริงคุณจะใช้งานExecutors.newFixedThreadPoolดีกว่ามากซึ่งจะให้พูลที่มีจำนวนเธรดคงที่หรือใช้คลาส ThreadPoolExecutor โดยตรงเพื่อการควบคุมสูงสุด "
ถ้าคุณดูซอร์สโค้ดคุณจะเห็นว่าพวกมันกำลังเรียกThreadPoolExecutor ภายในและการตั้งค่าคุณสมบัติของพวกเขา คุณสามารถสร้างสิ่งที่คุณต้องการควบคุมได้ดีขึ้น
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>());
}
หากคุณไม่กังวลเกี่ยวกับคิวงานCallable / Runnable ที่ไม่มีขอบเขตคุณสามารถใช้หนึ่งในนั้น ที่แนะนำโดย bruno ผมก็ต้องการnewFixedThreadPool
ที่จะnewCachedThreadPool
มากกว่าสองคนนี้
แต่ThreadPoolExecutor มีคุณสมบัติที่ยืดหยุ่นมากกว่าเมื่อเทียบกับnewFixedThreadPool
หรือnewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ข้อดี:
คุณสามารถควบคุมขนาดBlockingQueueได้อย่างเต็มที่ มันไม่ได้ไม่มีขอบเขตแตกต่างจากสองตัวเลือกก่อนหน้านี้ ฉันจะไม่ได้รับข้อผิดพลาดหน่วยความจำไม่เพียงพอเนื่องจากมีงาน Callable / Runnable ที่ค้างอยู่จำนวนมากเมื่อระบบปั่นป่วนไม่คาดคิด
คุณสามารถใช้นโยบายการจัดการการปฏิเสธที่กำหนดเองหรือใช้หนึ่งในนโยบาย:
ในค่าเริ่มต้นThreadPoolExecutor.AbortPolicy
ตัวจัดการจะโยนรันไทม์ RejectedExecutionException เมื่อถูกปฏิเสธ
ในThreadPoolExecutor.CallerRunsPolicy
เธรดที่เรียกใช้เรียกใช้งานตัวเองจะทำงาน นี่เป็นกลไกการควบคุมป้อนกลับอย่างง่ายซึ่งจะลดอัตราการส่งงานใหม่
ในThreadPoolExecutor.DiscardPolicy
งานที่ไม่สามารถดำเนินการได้จะถูกทิ้งไว้เพียงแค่
ในThreadPoolExecutor.DiscardOldestPolicy
หากผู้บริหารไม่ได้ปิดตัวลงงานที่ส่วนหัวของคิวงานจะถูกดร็อปและจากนั้นจะพยายามดำเนินการใหม่ (ซึ่งอาจล้มเหลวอีกครั้งทำให้สิ่งนี้ซ้ำไปซ้ำมา)
คุณสามารถใช้โรงงานด้ายที่กำหนดเองสำหรับกรณีการใช้งานด้านล่าง:
ถูกตัอง, Executors.newCachedThreadPool()
ไม่ใช่ตัวเลือกที่ยอดเยี่ยมสำหรับรหัสเซิร์ฟเวอร์ที่ให้บริการลูกค้าหลายรายและคำขอพร้อมกัน
ทำไม? โดยทั่วไปมีสองปัญหา (ที่เกี่ยวข้อง) กับมัน:
มันไม่ได้ จำกัด ซึ่งหมายความว่าคุณกำลังเปิดประตูให้ใครก็ตามที่จะทำลาย JVM ของคุณโดยเพียงแค่ฉีดงานเข้าไปในบริการ (การโจมตี DoS) เธรดใช้จำนวนหน่วยความจำไม่น้อยและเพิ่มปริมาณการใช้หน่วยความจำตามความคืบหน้าการทำงานดังนั้นจึงค่อนข้างง่ายที่จะโค่นเซิร์ฟเวอร์ด้วยวิธีนี้ (เว้นแต่คุณจะมีเซอร์กิตเบรกเกอร์อยู่
ปัญหาที่ไม่ได้ จำกัด ขอบเขตนั้นรุนแรงขึ้นโดยความจริงที่ว่า Executor ต้องเผชิญกับปัญหาSynchronousQueue
ซึ่งหมายความว่ามีการส่งมอบโดยตรงระหว่าง task-giver และ thread pool งานใหม่แต่ละงานจะสร้างเธรดใหม่หากเธรดที่มีอยู่ทั้งหมดไม่ว่าง นี่เป็นกลยุทธ์ที่ไม่ดีสำหรับรหัสเซิร์ฟเวอร์ เมื่อ CPU อิ่มตัวงานที่มีอยู่จะใช้เวลานานกว่าจะเสร็จสิ้น ยังมีการส่งงานเพิ่มเติมและสร้างเธรดเพิ่มขึ้นดังนั้นงานจึงใช้เวลาในการดำเนินการนานกว่าและนานกว่า เมื่อซีพียูอิ่มตัวเธรดจำนวนมากจะไม่ใช่สิ่งที่เซิร์ฟเวอร์ต้องการ
นี่คือคำแนะนำของฉัน:
ใช้เธรดพูลขนาดคงที่Executors.newFixedThreadPoolหรือ ThreadPoolExecutor ด้วยจำนวนเธรดสูงสุดที่ตั้งค่าไว้
ThreadPoolExecutor
ระดับเป็นการนำฐานสำหรับผู้จัดการที่จะถูกส่งกลับจากหลายExecutors
วิธีโรงงาน ดังนั้นเรามาใกล้คงที่และแคชเธรดพูลจากThreadPoolExecutor
มุมมองของ
ตัวสร้างหลักของคลาสนี้มีลักษณะดังนี้:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
corePoolSize
กำหนดขนาดต่ำสุดของสระว่ายน้ำด้ายเป้าหมาย การใช้งานจะรักษากลุ่มของขนาดนั้นแม้ว่าจะไม่มีงานที่ต้องดำเนินการ
maximumPoolSize
เป็นจำนวนสูงสุดของเธรดที่สามารถใช้งานได้ในครั้งเดียว
หลังจากเธรดพูลโตขึ้นและใหญ่กว่าcorePoolSize
ขีด จำกัด ผู้ดำเนินการสามารถยกเลิกเธรดที่ไม่ได้ทำงานและไปยังเธรดcorePoolSize
อีกครั้ง หากallowCoreThreadTimeOut
เป็นจริงผู้ดำเนินการยังสามารถยกเลิกเธรดพูลเธรดได้หากไม่มีการใช้งานมากกว่าkeepAliveTime
ขีด จำกัด
ดังนั้นบรรทัดล่างคือถ้าเธรดยังคงว่างมากกว่า keepAliveTime
ขีด จำกัด พวกเขาอาจถูกยกเลิกเนื่องจากไม่มีความต้องการ
จะเกิดอะไรขึ้นเมื่อมีภารกิจใหม่เข้ามาและเธรดหลักทั้งหมดจะถูกครอบครอง? งานใหม่จะถูกจัดคิวไว้ภายในBlockingQueue<Runnable>
อินสแตนซ์นั้น เมื่อเธรดว่างแล้วภารกิจงานที่อยู่ในคิวงานใดงานหนึ่งสามารถประมวลผลได้
มีการใช้งานที่แตกต่างกันของBlockingQueue
อินเตอร์เฟซใน Java ดังนั้นเราจึงสามารถใช้วิธีการจัดคิวที่แตกต่างกันเช่น:
คิวที่ถูกผูกไว้ : งานใหม่จะถูกจัดคิวภายในคิวงานที่ถูกผูกไว้
Unbounded Queue : งานใหม่จะถูกจัดคิวภายในคิวงานที่ไม่ได้ถูก จำกัด ดังนั้นคิวนี้สามารถเติบโตได้มากเท่ากับขนาดฮีปที่อนุญาต
Synoffous Handoff : เรายังสามารถใช้SynchronousQueue
เพื่อจัดคิวงานใหม่ ในกรณีนั้นเมื่อเข้าคิวงานใหม่เธรดอื่นจะต้องรองานนั้นอยู่
นี่คือวิธีการThreadPoolExecutor
ดำเนินงานใหม่:
corePoolSize
เธรดกำลังทำงานอยู่ให้พยายามเริ่มเธรดใหม่ด้วยงานที่กำหนดเป็นงานแรกBlockingQueue#offer
วิธีการ offer
วิธีจะไม่ปิดกั้นถ้าคิวเต็มและผลตอบแทนทันทีfalse
วิธีจะไม่ปิดกั้นถ้าคิวเต็มและผลตอบแทนทันทีoffer
ส่งคืนfalse
) ก็จะพยายามเพิ่มเธรดใหม่ไปยังกลุ่มเธรดที่มีงานนี้เป็นงานแรกRejectedExecutionHandler
มาความแตกต่างที่สำคัญระหว่างกลุ่มเธรดที่คงที่และแคชจะลดลงจนถึงปัจจัยทั้งสามนี้:
+ ----------- ----------- + + + ------------------- ----- ---------------------------- + | ประเภทสระน้ำ ขนาดแกน ขนาดสูงสุด กลยุทธ์การจัดคิว + ----------- ----------- + + + ------------------- ----- ---------------------------- + | แก้ไข | n (แก้ไข) | n (แก้ไข) | เชื่อมโยงไร้ขีด จำกัด + ----------- ----------- + + + ------------------- ----- ---------------------------- + | แคช | 0 | จำนวนเต็ม MAX_VALUE | `SynchronousQueue` | + ----------- ----------- + + + ------------------- ----- ---------------------------- +
Excutors.newFixedThreadPool(n)
ทำงาน:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
อย่างที่เห็น:
OutOfMemoryError
เป็นโชคร้ายพอที่เราอาจพบการเมื่อใดที่ฉันควรใช้รายการใดรายการหนึ่ง กลยุทธ์ใดดีกว่าในแง่ของการใช้ทรัพยากร
สระว่ายน้ำด้ายขนาดคงที่ดูเหมือนว่าจะเป็นผู้สมัครที่ดีเมื่อเรากำลังจะ จำกัด จำนวนของงานที่เกิดขึ้นพร้อมกันเพื่อการบริหารทรัพยากร
ตัวอย่างเช่นถ้าเราจะใช้ตัวจัดการเพื่อจัดการคำขอของเว็บเซิร์ฟเวอร์ตัวจัดการแบบตายตัวสามารถจัดการกับคำขอที่มีการระเบิดได้อย่างสมเหตุสมผล
สำหรับการจัดการทรัพยากรที่ดียิ่งขึ้นก็ขอแนะนำในการสร้างที่กำหนดเองThreadPoolExecutor
ที่มีขอบเขตการดำเนินงานควบคู่ไปกับการที่เหมาะสมBlockingQueue<T>
RejectedExecutionHandler
นี่คือวิธีการExecutors.newCachedThreadPool()
ทำงาน:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
อย่างที่เห็น:
Integer.MAX_VALUE
สระว่ายน้ำด้ายสามารถเจริญเติบโตได้จากศูนย์หัวข้อที่จะ จริงแล้วเธรดพูลนั้นไม่ได้ถูก จำกัด ขอบเขตSynchronousQueue
ล้มเหลวเสมอเมื่อไม่มีอีกปลายหนึ่งที่จะยอมรับมัน!เมื่อใดที่ฉันควรใช้รายการใดรายการหนึ่ง กลยุทธ์ใดดีกว่าในแง่ของการใช้ทรัพยากร
ใช้มันเมื่อคุณมีงานที่คาดเดาได้สั้น ๆ มากมาย
คุณต้องใช้ newCachedThreadPool เฉพาะเมื่อคุณมีงานอะซิงโครนัสระยะสั้นตามที่ระบุไว้ใน Javadoc หากคุณส่งงานที่ต้องใช้เวลานานในการประมวลผลคุณจะสิ้นสุดการสร้างเธรดจำนวนมากเกินไป คุณอาจไปถึง CPU 100% หากคุณส่งงานที่ต้องใช้เวลานานในอัตราที่เร็วกว่าไปยัง newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ )
ฉันทำการทดสอบอย่างรวดเร็วและพบสิ่งต่อไปนี้:
1) ถ้าใช้ SynchronousQueue:
หลังจากเธรดมีขนาดสูงสุดงานใหม่ใด ๆ จะถูกปฏิเสธด้วยข้อยกเว้นดังนี้
ข้อยกเว้นในเธรด "main" java.util.concurrent.RejectedExecutionException: ภารกิจ java.util.concurrent.FutureTask@3fee733d ถูกปฏิเสธจาก java.util.concurrent.ThreadPoolExecutor@5acf9800 [การทำงานขนาดคิว = 3, งานที่อยู่ในคิว = 3, งานที่อยู่ในคิว = 3, งานที่อยู่ในคิว = 3, งานที่จัดคิว = 0 งานที่เสร็จสมบูรณ์ = 0]
ที่ java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)
2) ถ้าใช้ LinkedBlockingQueue:
เธรดไม่เคยเพิ่มจากขนาดต่ำสุดเป็นขนาดสูงสุดหมายความว่ากลุ่มเธรดมีขนาดคงที่เป็นขนาดต่ำสุด