Executors.newCachedThreadPool () และ Executors.newFixedThreadPool ()


คำตอบ:


202

ฉันคิดว่าเอกสารอธิบายความแตกต่างและการใช้งานของทั้งสองฟังก์ชันได้ค่อนข้างดี:

newFixedThreadPool

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

newCachedThreadPool

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

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

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


1
ใช่ฉันได้ผ่าน docs .. ปัญหาคือ ... fixedThreadPool ทำให้เกิดข้อผิดพลาดของหน่วยความจำ @ 3 กระทู้ .. ที่ cachedPool ภายในสร้างเพียงเธรดเดียว .. ในการเพิ่มขนาดฮีปฉันได้รับเหมือนกัน การแสดงสำหรับทั้งสอง .. มีอะไรอีกที่ฉันหายไป !!
hakish

1
คุณกำลังให้ Threadfactory ใด ๆ กับ ThreadPool หรือไม่ ฉันเดาว่าอาจจะเก็บสถานะบางอย่างในกระทู้ที่ไม่ได้เก็บขยะ หากไม่ใช่อาจเป็นเพราะโปรแกรมของคุณกำลังทำงานอยู่ใกล้กับขนาด จำกัด ของฮีปที่มีการสร้างเธรด 3 เธรดซึ่งจะทำให้เกิด OutOfMemory นอกจากนี้ถ้า cachedPool กำลังสร้างเธรดเดียวเท่านั้นภายในสิ่งนี้เป็นไปได้บ่งชี้ว่างานของคุณกำลังเรียกใช้ syncronhized
bruno conde

@brunoconde เช่นเดียวกับ @Louis F. ชี้ให้เห็นว่าnewCachedThreadPoolอาจทำให้เกิดปัญหาร้ายแรงบางอย่างเนื่องจากคุณปล่อยให้การควบคุมทั้งหมดthread poolและเมื่อบริการกำลังทำงานร่วมกับผู้อื่นในโฮสต์เดียวกันซึ่งอาจทำให้เกิดปัญหาอื่น ๆ เนื่องจาก CPU รอเป็นเวลานาน ดังนั้นฉันคิดว่าnewFixedThreadPoolจะปลอดภัยมากขึ้นในสถานการณ์ประเภทนี้ นอกจากนี้โพสต์นี้จะอธิบายความแตกต่างที่โดดเด่นที่สุดระหว่างพวกเขา
ได้ยิน

75

เพียงเพื่อทำคำตอบอื่น ๆ ให้สมบูรณ์ฉันต้องการเสนอราคา Java รุ่นที่ 2 โดย Joshua Bloch ตอนที่ 10 ข้อ 68:

"การเลือกบริการตัวจัดการสำหรับแอปพลิเคชันเฉพาะอาจเป็นเรื่องยากหากคุณกำลังเขียน โปรแกรมขนาดเล็กหรือเซิร์ฟเวอร์ที่โหลดเบา ๆโดยใช้Executors.New- CachedThreadPool นั้นเป็นตัวเลือกที่ดีเนื่องจากไม่ต้องการการกำหนดค่าและโดยทั่วไปแล้ว" สิ่งที่ถูกต้อง." แต่กลุ่มเธรดที่แคชไว้ไม่ใช่ตัวเลือกที่ดีสำหรับเซิร์ฟเวอร์ที่ใช้งานจริงจำนวนมาก !

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

ดังนั้นในเซิร์ฟเวอร์ที่ใช้งานจริงคุณจะใช้งานExecutors.newFixedThreadPoolดีกว่ามากซึ่งจะให้พูลที่มีจำนวนเธรดคงที่หรือใช้คลาส ThreadPoolExecutor โดยตรงเพื่อการควบคุมสูงสุด "


15

ถ้าคุณดูซอร์สโค้ดคุณจะเห็นว่าพวกมันกำลังเรียก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>());
}

1
ผู้ดำเนินการเธรดแคชที่มีขีด จำกัด บนสติและพูดว่าการเก็บข้อมูลที่ไม่ทำงาน 5-10 นาทีเหมาะสำหรับทุกโอกาส
Agoston Horvath

12

หากคุณไม่กังวลเกี่ยวกับคิวงานCallable / Runnable ที่ไม่มีขอบเขตคุณสามารถใช้หนึ่งในนั้น ที่แนะนำโดย bruno ผมก็ต้องการnewFixedThreadPoolที่จะnewCachedThreadPoolมากกว่าสองคนนี้

แต่ThreadPoolExecutor มีคุณสมบัติที่ยืดหยุ่นมากกว่าเมื่อเทียบกับnewFixedThreadPoolหรือnewCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

ข้อดี:

  1. คุณสามารถควบคุมขนาดBlockingQueueได้อย่างเต็มที่ มันไม่ได้ไม่มีขอบเขตแตกต่างจากสองตัวเลือกก่อนหน้านี้ ฉันจะไม่ได้รับข้อผิดพลาดหน่วยความจำไม่เพียงพอเนื่องจากมีงาน Callable / Runnable ที่ค้างอยู่จำนวนมากเมื่อระบบปั่นป่วนไม่คาดคิด

  2. คุณสามารถใช้นโยบายการจัดการการปฏิเสธที่กำหนดเองหรือใช้หนึ่งในนโยบาย:

    1. ในค่าเริ่มต้นThreadPoolExecutor.AbortPolicyตัวจัดการจะโยนรันไทม์ RejectedExecutionException เมื่อถูกปฏิเสธ

    2. ในThreadPoolExecutor.CallerRunsPolicyเธรดที่เรียกใช้เรียกใช้งานตัวเองจะทำงาน นี่เป็นกลไกการควบคุมป้อนกลับอย่างง่ายซึ่งจะลดอัตราการส่งงานใหม่

    3. ในThreadPoolExecutor.DiscardPolicyงานที่ไม่สามารถดำเนินการได้จะถูกทิ้งไว้เพียงแค่

    4. ในThreadPoolExecutor.DiscardOldestPolicyหากผู้บริหารไม่ได้ปิดตัวลงงานที่ส่วนหัวของคิวงานจะถูกดร็อปและจากนั้นจะพยายามดำเนินการใหม่ (ซึ่งอาจล้มเหลวอีกครั้งทำให้สิ่งนี้ซ้ำไปซ้ำมา)

  3. คุณสามารถใช้โรงงานด้ายที่กำหนดเองสำหรับกรณีการใช้งานด้านล่าง:

    1. หากต้องการตั้งชื่อเธรดที่มีความหมายมากขึ้น
    2. เพื่อตั้งสถานะ thread daemon
    3. เพื่อกำหนดลำดับความสำคัญของเธรด

11

ถูกตัอง, Executors.newCachedThreadPool()ไม่ใช่ตัวเลือกที่ยอดเยี่ยมสำหรับรหัสเซิร์ฟเวอร์ที่ให้บริการลูกค้าหลายรายและคำขอพร้อมกัน

ทำไม? โดยทั่วไปมีสองปัญหา (ที่เกี่ยวข้อง) กับมัน:

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

  2. ปัญหาที่ไม่ได้ จำกัด ขอบเขตนั้นรุนแรงขึ้นโดยความจริงที่ว่า Executor ต้องเผชิญกับปัญหาSynchronousQueueซึ่งหมายความว่ามีการส่งมอบโดยตรงระหว่าง task-giver และ thread pool งานใหม่แต่ละงานจะสร้างเธรดใหม่หากเธรดที่มีอยู่ทั้งหมดไม่ว่าง นี่เป็นกลยุทธ์ที่ไม่ดีสำหรับรหัสเซิร์ฟเวอร์ เมื่อ CPU อิ่มตัวงานที่มีอยู่จะใช้เวลานานกว่าจะเสร็จสิ้น ยังมีการส่งงานเพิ่มเติมและสร้างเธรดเพิ่มขึ้นดังนั้นงานจึงใช้เวลาในการดำเนินการนานกว่าและนานกว่า เมื่อซีพียูอิ่มตัวเธรดจำนวนมากจะไม่ใช่สิ่งที่เซิร์ฟเวอร์ต้องการ

นี่คือคำแนะนำของฉัน:

ใช้เธรดพูลขนาดคงที่Executors.newFixedThreadPoolหรือ ThreadPoolExecutor ด้วยจำนวนเธรดสูงสุดที่ตั้งค่าไว้


6

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

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 ดังนั้นเราจึงสามารถใช้วิธีการจัดคิวที่แตกต่างกันเช่น:

  1. คิวที่ถูกผูกไว้ : งานใหม่จะถูกจัดคิวภายในคิวงานที่ถูกผูกไว้

  2. Unbounded Queue : งานใหม่จะถูกจัดคิวภายในคิวงานที่ไม่ได้ถูก จำกัด ดังนั้นคิวนี้สามารถเติบโตได้มากเท่ากับขนาดฮีปที่อนุญาต

  3. Synoffous Handoff : เรายังสามารถใช้SynchronousQueueเพื่อจัดคิวงานใหม่ ในกรณีนั้นเมื่อเข้าคิวงานใหม่เธรดอื่นจะต้องรองานนั้นอยู่

การส่งงาน

นี่คือวิธีการThreadPoolExecutorดำเนินงานใหม่:

  1. ถ้าน้อยกว่า corePoolSizeเธรดกำลังทำงานอยู่ให้พยายามเริ่มเธรดใหม่ด้วยงานที่กำหนดเป็นงานแรก
  2. มิฉะนั้นจะพยายามจัดคิวงานใหม่โดยใช้ BlockingQueue#offerวิธีการ offerวิธีจะไม่ปิดกั้นถ้าคิวเต็มและผลตอบแทนทันทีfalseวิธีจะไม่ปิดกั้นถ้าคิวเต็มและผลตอบแทนทันที
  3. หากไม่สามารถจัดคิวงานใหม่ (เช่นofferส่งคืนfalse) ก็จะพยายามเพิ่มเธรดใหม่ไปยังกลุ่มเธรดที่มีงานนี้เป็นงานแรก
  4. หากไม่สามารถเพิ่มเธรดใหม่ได้ผู้ปฏิบัติการจะปิดระบบหรืออิ่มตัว ไม่ว่าจะด้วยวิธีใดงานใหม่จะถูกปฏิเสธโดยใช้งานที่ให้RejectedExecutionHandlerมา

ความแตกต่างที่สำคัญระหว่างกลุ่มเธรดที่คงที่และแคชจะลดลงจนถึงปัจจัยทั้งสามนี้:

  1. ขนาดพูของแกน
  2. ขนาดสระสูงสุด
  3. การจัดคิว
+ ----------- ----------- + + + ------------------- ----- ---------------------------- +
| ประเภทสระน้ำ ขนาดแกน ขนาดสูงสุด กลยุทธ์การจัดคิว
+ ----------- ----------- + + + ------------------- ----- ---------------------------- +
| แก้ไข | 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สระว่ายน้ำด้ายสามารถเจริญเติบโตได้จากศูนย์หัวข้อที่จะ จริงแล้วเธรดพูลนั้นไม่ได้ถูก จำกัด ขอบเขต
  • หากเธรดใด ๆ ไม่ได้ใช้งานนานกว่า 1 นาทีเธรดนั้นอาจถูกยกเลิก ดังนั้นพูลสามารถย่อขนาดได้ถ้าเธรดยังคงว่างอยู่มากเกินไป
  • หากเธรดที่จัดสรรทั้งหมดถูกครอบครองในขณะที่มีงานใหม่เข้ามามันจะสร้างเธรดใหม่ตามที่เสนองานใหม่ให้กับการSynchronousQueueล้มเหลวเสมอเมื่อไม่มีอีกปลายหนึ่งที่จะยอมรับมัน!

เมื่อใดที่ฉันควรใช้รายการใดรายการหนึ่ง กลยุทธ์ใดดีกว่าในแง่ของการใช้ทรัพยากร

ใช้มันเมื่อคุณมีงานที่คาดเดาได้สั้น ๆ มากมาย


5

คุณต้องใช้ newCachedThreadPool เฉพาะเมื่อคุณมีงานอะซิงโครนัสระยะสั้นตามที่ระบุไว้ใน Javadoc หากคุณส่งงานที่ต้องใช้เวลานานในการประมวลผลคุณจะสิ้นสุดการสร้างเธรดจำนวนมากเกินไป คุณอาจไปถึง CPU 100% หากคุณส่งงานที่ต้องใช้เวลานานในอัตราที่เร็วกว่าไปยัง newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ )


1

ฉันทำการทดสอบอย่างรวดเร็วและพบสิ่งต่อไปนี้:

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:

เธรดไม่เคยเพิ่มจากขนาดต่ำสุดเป็นขนาดสูงสุดหมายความว่ากลุ่มเธรดมีขนาดคงที่เป็นขนาดต่ำสุด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.