FixedThreadPool vs CachedThreadPool: ít tệ nạn hơn


93

Tôi có một chương trình tạo ra các luồng (~ 5-150) thực hiện một loạt các tác vụ. Ban đầu, tôi sử dụng a FixedThreadPoolcâu hỏi tương tự này gợi ý rằng chúng phù hợp hơn với các tác vụ tồn tại lâu hơn và với kiến ​​thức rất hạn chế của tôi về đa luồng, tôi coi tuổi thọ trung bình của các luồng (vài phút) là " lâu dài ".

Tuy nhiên, gần đây tôi đã thêm khả năng tạo ra các luồng bổ sung và làm như vậy sẽ đưa tôi vượt quá giới hạn luồng mà tôi đã đặt. Trong trường hợp này, tốt hơn là nên đoán và tăng số chủ đề tôi có thể cho phép hoặc chuyển sang a CachedThreadPoolđể tôi không có chủ đề lãng phí?

Thử sơ bộ cả hai, dường như không sự khác biệt nên tôi có xu hướng chọn loại CachedThreadPoolvừa phải để tránh lãng phí. Tuy nhiên, tuổi thọ của các luồng có nghĩa là tôi nên chọn một FixedThreadPoolvà chỉ xử lý các luồng không sử dụng? Câu hỏi này có vẻ như những chủ đề bổ sung đó không bị lãng phí nhưng tôi sẽ đánh giá cao việc làm rõ.

Câu trả lời:


108

CachedThreadPool là chính xác những gì bạn nên sử dụng cho tình huống của mình vì không có hậu quả tiêu cực nào khi sử dụng một cho các luồng chạy dài. Nhận xét trong tài liệu java về việc CachedThreadPools phù hợp với các tác vụ ngắn chỉ đơn thuần gợi ý rằng chúng đặc biệt thích hợp cho những trường hợp như vậy, không phải là chúng không thể hoặc không nên được sử dụng cho các tác vụ liên quan đến các tác vụ chạy dài.

Để giải thích thêm, Executor.newCachedThreadPoolExecutor.newFixedThreadPool đều được hỗ trợ bởi cùng một triển khai nhóm luồng (ít nhất là trong JDK đang mở) chỉ với các tham số khác nhau. Sự khác biệt chỉ là tối thiểu, tối đa, thời gian ngắt luồng và loại hàng đợi của chúng.

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 có lợi thế khi bạn thực sự muốn làm việc với một số luồng cố định, vì vậy bạn có thể gửi bất kỳ số lượng tác vụ nào đến dịch vụ thực thi trong khi biết rằng số luồng sẽ được duy trì ở mức bạn đã chỉ định. Nếu bạn muốn tăng số lượng chủ đề một cách rõ ràng, thì đây không phải là lựa chọn thích hợp.

Tuy nhiên, điều này có nghĩa là một vấn đề mà bạn có thể gặp phải với CachedThreadPool liên quan đến việc giới hạn số lượng các luồng đang chạy đồng thời. CachedThreadPool sẽ không giới hạn chúng cho bạn, vì vậy bạn có thể cần phải viết mã của riêng mình để đảm bảo rằng bạn không chạy quá nhiều luồng. Điều này thực sự phụ thuộc vào thiết kế ứng dụng của bạn và cách các tác vụ được gửi tới dịch vụ thực thi.


1
"Một CachedThreadPool là chính xác những gì bạn nên sử dụng cho tình huống của mình vì không có hậu quả tiêu cực nào khi sử dụng một cho các luồng chạy dài". Tôi không nghĩ là tôi đồng ý. CachedThreadPool tự động tạo các luồng không có giới hạn trên. Các tác vụ chạy dài trên một số lượng lớn các luồng có thể có khả năng tiêu tốn tất cả các tài nguyên. Ngoài ra, có nhiều luồng hơn mức lý tưởng có thể dẫn đến lãng phí quá nhiều tài nguyên cho việc chuyển đổi ngữ cảnh của các luồng này. Mặc dù bạn đã giải thích ở cuối câu trả lời rằng điều chỉnh tùy chỉnh là bắt buộc, nhưng phần đầu của câu trả lời có một chút sai lệch.
Nishit

1
Tại sao không chỉ đơn giản là tạo một giới hạn ThreadPoolExecutorlike ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar,

45

Cả hai FixedThreadPoolCachedThreadPoolđều là tệ nạn trong các ứng dụng được tải nhiều.

CachedThreadPool nguy hiểm hơn FixedThreadPool

Nếu ứng dụng của bạn được tải nhiều và yêu cầu độ trễ thấp, tốt hơn nên loại bỏ cả hai tùy chọn do các nhược điểm dưới đây

  1. Tính chất không giới hạn của hàng đợi tác vụ: Nó có thể gây ra tình trạng hết bộ nhớ hoặc độ trễ cao
  2. Các luồng chạy lâu sẽ gây CachedThreadPoolmất kiểm soát khi tạo Chủ đề

Vì bạn biết rằng cả hai đều là xấu xa, điều xấu ít hơn sẽ không làm điều tốt. Thích ThreadPoolExecutor , cung cấp khả năng kiểm soát chi tiết trên nhiều tham số.

  1. Đặt hàng đợi tác vụ làm hàng đợi giới hạn để kiểm soát tốt hơn
  2. Có đúng RejectionHandler - Trình xử lý RejectionHandler hoặc Mặc định của riêng bạn do JDK cung cấp
  3. Nếu bạn phải làm gì đó trước / sau khi hoàn thành nhiệm vụ, hãy ghi đè beforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable)
  4. Ghi đè ThreadFactory nếu yêu cầu tùy chỉnh chuỗi
  5. Kiểm soát kích thước nhóm chủ đề động vào thời gian chạy (câu hỏi SE liên quan: Nhóm chủ đề động )

Điều gì sẽ xảy ra nếu ai đó quyết định sử dụng CommonPool?
Crosk Cool

1
@Ravindra - Bạn đã giải thích rõ ràng về nhược điểm của cả CachedThreadPool và FixedThreadPool. Điều này cho thấy bạn đã hiểu sâu sắc về gói đồng thời.
Ayaskant

5

Vì vậy, tôi có một chương trình tạo ra các luồng (~ 5-150) thực hiện một loạt các nhiệm vụ.

Bạn có chắc mình hiểu cách xử lý luồng thực sự được xử lý bởi hệ điều hành và phần cứng bạn chọn không? Cách Java ánh xạ luồng tới luồng hệ điều hành, cách ánh xạ luồng tới luồng CPU, v.v.? Tôi đang hỏi vì việc tạo 150 luồng trong ONE JRE chỉ có ý nghĩa nếu bạn có các lõi / luồng CPU lớn bên dưới, điều này rất có thể không phải như vậy. Tùy thuộc vào hệ điều hành và RAM được sử dụng, việc tạo nhiều hơn n luồng thậm chí có thể dẫn đến việc JRE của bạn bị chấm dứt do lỗi OOM. Vì vậy, bạn thực sự nên phân biệt giữa các luồng và công việc phải làm của các luồng đó, bạn thậm chí có thể xử lý bao nhiêu công việc, v.v.

Và đó là vấn đề với CachedThreadPool: Không có ý nghĩa gì khi xếp hàng dài các công việc đang chạy trong các luồng thực sự không thể chạy vì bạn chỉ có 2 lõi CPU có thể xử lý các luồng đó. Nếu bạn kết thúc với 150 luồng được lập lịch, bạn có thể tạo ra nhiều chi phí không cần thiết cho các bộ lập lịch được sử dụng trong Java và Hệ điều hành để xử lý đồng thời chúng. Điều này đơn giản là không thể nếu bạn chỉ có 2 lõi CPU, trừ khi các luồng của bạn luôn chờ I / O hoặc tương tự. Nhưng ngay cả trong trường hợp đó, nhiều luồng sẽ tạo ra rất nhiều I / O ...

Và vấn đề đó không xảy ra với FixedThreadPool, được tạo với ví dụ 2 + n luồng, trong đó n là mức thấp hợp lý, vì với phần cứng và tài nguyên hệ điều hành đó được sử dụng với chi phí thấp hơn nhiều để quản lý các luồng không thể chạy.


Đôi khi không có sự lựa chọn nào tốt hơn, bạn có thể chỉ có 1 lõi CPU nhưng nếu bạn đang chạy một máy chủ mà mọi yêu cầu của người dùng sẽ kích hoạt một luồng để xử lý yêu cầu, sẽ không có bất kỳ lựa chọn hợp lý nào khác, đặc biệt nếu bạn có kế hoạch để mở rộng máy chủ khi bạn phát triển cơ sở người dùng của mình.
Michel Feinstein

@mFeinstein Làm sao có thể không có lựa chọn nếu một người ở vị trí chọn triển khai nhóm luồng? Trong ví dụ của bạn với 1 lõi CPU chỉ tạo ra nhiều luồng hơn đơn giản là không có ý nghĩa gì, nó hoàn toàn phù hợp với ví dụ của tôi bằng cách sử dụng FixedThreadPool. Quy mô đó cũng dễ dàng, đầu tiên với trên hoặc hai luồng công nhân, sau đó với 10 hoặc 15 tùy thuộc vào số lượng lõi.
Thorsten Schöning

2
Phần lớn các triển khai máy chủ web sẽ tạo một luồng mới cho mỗi yêu cầu HTTP mới ... Họ sẽ không quan tâm đến việc máy có bao nhiêu lõi thực tế, điều này làm cho việc triển khai đơn giản hơn và dễ mở rộng hơn. Điều này áp dụng cho nhiều thiết kế khác mà bạn chỉ muốn viết mã một lần và triển khai, và không phải biên dịch lại và triển khai lại nếu bạn thay đổi máy, có thể là một phiên bản đám mây.
Michel Feinstein

@mFeinstein Hầu hết các máy chủ web sử dụng nhóm luồng cho các yêu cầu của riêng chúng, đơn giản vì việc tạo ra các luồng không thể chạy không có ý nghĩa hoặc chúng sử dụng các vòng lặp sự kiện cho các kết nối và xử lý các yêu cầu trong nhóm sau đó hoặc tương tự. Ngoài ra, bạn đang thiếu điểm, đó là câu hỏi về việc một người có thể chọn nhóm luồng chính xác và các luồng sinh sản không thể chạy bất cứ lúc nào vẫn không có ý nghĩa. Một FixedthreadPool được định cấu hình thành một lượng luồng hợp lý cho mỗi máy tùy thuộc vào quy mô lõi.
Thorsten Schöning

3
@ ThorstenSchöning, có 50 luồng ràng buộc CPU trên máy 2 lõi là vô ích. Có 50 luồng liên kết IO trên máy 2 lõi có thể rất hữu ích.
Paul Draper,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.