Executors.newCachedThreadPool () so với Executors.newFixedThreadPool ()


Câu trả lời:


202

Tôi nghĩ rằng các tài liệu giải thích sự khác biệt và cách sử dụng của hai chức năng này khá tốt:

newFixedThreadPool

Tạo nhóm luồng sử dụng lại một số luồng cố định hoạt động ngoài hàng đợi không giới hạn được chia sẻ. Tại bất kỳ thời điểm nào, hầu hết các luồng nThreads sẽ là các tác vụ xử lý đang hoạt động. Nếu các tác vụ bổ sung được gửi khi tất cả các luồng đang hoạt động, chúng sẽ đợi trong hàng đợi cho đến khi một luồng có sẵn. Nếu bất kỳ luồng nào kết thúc do lỗi trong khi thực hiện trước khi tắt máy, một luồng mới sẽ thay thế nếu cần để thực hiện các tác vụ tiếp theo. Các chủ đề trong nhóm sẽ tồn tại cho đến khi nó được tắt một cách rõ ràng.

newCachedThreadPool

Tạo một nhóm luồng tạo ra các luồng mới khi cần, nhưng sẽ sử dụng lại các luồng được xây dựng trước đó khi chúng có sẵn. Các nhóm này thường sẽ cải thiện hiệu suất của các chương trình thực thi nhiều tác vụ không đồng bộ trong thời gian ngắn. Các lệnh gọi để thực thi sẽ sử dụng lại các luồng được xây dựng trước đó nếu có. Nếu không có chủ đề hiện có, một chủ đề mới sẽ được tạo và thêm vào nhóm. Các chủ đề không được sử dụng trong sáu mươi giây sẽ bị chấm dứt và xóa khỏi bộ đệm. Do đó, một nhóm không hoạt động đủ lâu sẽ không tiêu tốn bất kỳ tài nguyên nào. Lưu ý rằng các nhóm có thuộc tính tương tự nhưng chi tiết khác nhau (ví dụ: tham số hết thời gian chờ) có thể được tạo bằng các hàm tạo ThreadPoolExecutor.

Về tài nguyên, newFixedThreadPoolsẽ giữ tất cả các luồng chạy cho đến khi chúng bị chấm dứt rõ ràng. Trong các newCachedThreadPoolChủ đề không được sử dụng trong sáu mươi giây sẽ bị chấm dứt và xóa khỏi bộ đệm.

Vì điều này, việc tiêu thụ tài nguyên sẽ phụ thuộc rất nhiều vào tình huống. Ví dụ, nếu bạn có một số lượng lớn các tác vụ chạy dài, tôi sẽ đề xuất FixedThreadPool. Đối với CachedThreadPool, các tài liệu nói rằng "Những nhóm này thường sẽ cải thiện hiệu suất của các chương trình thực thi nhiều tác vụ không đồng bộ trong thời gian ngắn".


1
vâng, tôi đã trải qua các tài liệu..những vấn đề là ... fixedThreadPool đang gây ra lỗi bộ nhớ @ 3 luồng .. trong khi bộ nhớ cache được tạo trong nội bộ chỉ tạo một luồng duy nhất..trên tăng kích thước heap tôi cũng nhận được hiệu suất cho cả hai .. tôi còn thiếu gì nữa !!
hakish

1
Bạn đang cung cấp bất kỳ Threadfactory cho ThreadPool? Tôi đoán là có thể lưu trữ một số trạng thái trong các luồng không được thu gom rác. Nếu không, có thể chương trình của bạn đang chạy rất gần với kích thước giới hạn heap mà với việc tạo ra 3 luồng, nó gây ra OutOfMemory. Ngoài ra, nếu cacheedPool chỉ tạo nội bộ một luồng duy nhất thì điều này có thể chỉ ra rằng các tác vụ của bạn đang chạy được đồng bộ hóa.
bruno conde

@brunoconde Giống như @Louis F. chỉ ra rằng newCachedThreadPoolcó thể gây ra một số vấn đề nghiêm trọng vì bạn để mọi quyền kiểm soát thread poolvà khi dịch vụ này hoạt động với những người khác trong cùng một máy chủ , điều này có thể khiến những người khác gặp sự cố do chờ CPU trong thời gian dài. Vì vậy, tôi nghĩ rằng newFixedThreadPoolcó thể an toàn hơn trong loại kịch bản này. Ngoài ra bài đăng này làm rõ sự khác biệt nổi bật nhất giữa chúng.
Nghe

75

Chỉ cần hoàn thành các câu trả lời khác, tôi muốn trích dẫn Java hiệu quả, Phiên bản 2, của Joshua Bloch, chương 10, Mục 68:

"Chọn dịch vụ thực thi cho một ứng dụng cụ thể có thể khó khăn. Nếu bạn đang viết một chương trình nhỏ hoặc máy chủ tải nhẹ , sử dụng Executors.new- CachedThreadPool nói chung là một lựa chọn tốt , vì nó không yêu cầu cấu hình và nói chung là điều đúng." Nhưng một nhóm luồng được lưu trữ không phải là một lựa chọn tốt cho một máy chủ sản xuất được tải nặng !

Trong nhóm luồng được lưu trữ , các tác vụ đã gửi không được xếp hàng mà ngay lập tức được chuyển sang một luồng để thực thi. Nếu không có chủ đề có sẵn, một chủ đề mới được tạo ra . Nếu một máy chủ được tải quá nhiều đến nỗi tất cả các CPU của nó được sử dụng đầy đủ và sẽ có nhiều tác vụ hơn, nhiều luồng sẽ được tạo ra, điều này sẽ chỉ làm cho vấn đề tồi tệ hơn.

Do đó, trong một máy chủ sản xuất được tải nhiều , bạn nên sử dụng Executors.newFixedThreadPool , cung cấp cho bạn một nhóm có số lượng luồng cố định hoặc sử dụng trực tiếp lớp ThreadPoolExecutor để kiểm soát tối đa. "


15

Nếu bạn nhìn vào mã nguồn , bạn sẽ thấy, họ đang gọi ThreadPoolExecutor. nội bộ và thiết lập các thuộc tính của họ. Bạn có thể tạo một cái của bạn để kiểm soát tốt hơn yêu cầu của bạn.

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
Chính xác, một người thực hiện chủ đề được lưu trong bộ nhớ cache với giới hạn trên lành mạnh và nói, 5-10 phút gặt hái nhàn rỗi là hoàn hảo cho hầu hết các trường hợp.
Agoston Horvath

12

Nếu bạn không lo lắng về hàng đợi các nhiệm vụ có thể gọi / Chạy được không giới hạn , bạn có thể sử dụng một trong số chúng. Theo đề nghị của bruno, tôi quá thích newFixedThreadPoolđểnewCachedThreadPool hơn hai này.

Nhưng ThreadPoolExecutor cung cấp các tính năng linh hoạt hơn so với newFixedThreadPoolhoặcnewCachedThreadPool

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

Ưu điểm:

  1. Bạn có toàn quyền kiểm soát kích thước BlockingQueue . Nó không bị ràng buộc, không giống như hai tùy chọn trước đó. Tôi sẽ không bị lỗi bộ nhớ do một đống lớn các tác vụ Có thể gọi / Có thể chạy đang chờ xử lý khi có nhiễu loạn bất ngờ trong hệ thống.

  2. Bạn có thể thực hiện chính sách xử lý Từ chối tùy chỉnh HOẶC sử dụng một trong các chính sách:

    1. Trong mặc định ThreadPoolExecutor.AbortPolicy, trình xử lý ném một thời gian chạy RejectionExecutException sau khi từ chối.

    2. Trong ThreadPoolExecutor.CallerRunsPolicy, luồng mà thực hiện chính nó chạy nhiệm vụ. Điều này cung cấp một cơ chế kiểm soát phản hồi đơn giản sẽ làm chậm tốc độ mà các tác vụ mới được gửi.

    3. Trong ThreadPoolExecutor.DiscardPolicy, một nhiệm vụ không thể được thực hiện chỉ đơn giản là bị loại bỏ.

    4. Trong ThreadPoolExecutor.DiscardOldestPolicy, nếu người thi hành không tắt, tác vụ ở đầu hàng đợi công việc sẽ bị hủy và sau đó thực thi được thử lại (có thể thất bại lần nữa, khiến điều này được lặp lại.)

  3. Bạn có thể triển khai một nhà máy Thread tùy chỉnh cho các trường hợp sử dụng dưới đây:

    1. Để đặt một tên chủ đề mô tả hơn
    2. Để đặt trạng thái trình nền của chủ đề
    3. Để đặt ưu tiên luồng

11

Điều đó đúng, Executors.newCachedThreadPool()không phải là một lựa chọn tuyệt vời cho mã máy chủ phục vụ nhiều khách hàng và yêu cầu đồng thời.

Tại sao? Về cơ bản có hai vấn đề (liên quan) với nó:

  1. Nó không bị ràng buộc, điều đó có nghĩa là bạn đang mở cửa cho bất kỳ ai làm tê liệt JVM của bạn bằng cách tiêm thêm công việc vào dịch vụ (tấn công DoS). Các luồng tiêu thụ một lượng bộ nhớ không đáng kể và cũng tăng mức tiêu thụ bộ nhớ dựa trên tiến trình làm việc của chúng, do đó khá dễ dàng để lật đổ máy chủ theo cách này (trừ khi bạn có các bộ ngắt mạch khác).

  2. Vấn đề không bị ràng buộc trở nên trầm trọng hơn bởi thực tế là Executor đứng trước SynchronousQueueđiều đó có nghĩa là có sự chuyển giao trực tiếp giữa người trao nhiệm vụ và nhóm luồng. Mỗi tác vụ mới sẽ tạo ra một luồng mới nếu tất cả các luồng hiện có đang bận. Đây thường là một chiến lược tồi cho mã máy chủ. Khi CPU bị bão hòa, các tác vụ hiện tại mất nhiều thời gian hơn để hoàn thành. Tuy nhiên, nhiều nhiệm vụ đang được gửi và nhiều chủ đề được tạo ra, vì vậy các nhiệm vụ mất nhiều thời gian hơn và lâu hơn để hoàn thành. Khi CPU đã bão hòa, nhiều luồng hơn chắc chắn không phải là thứ mà máy chủ cần.

Dưới đây là khuyến nghị của tôi:

Sử dụng nhóm luồng có kích thước cố định Executors.newFixedThreadPool hoặc ThreadPoolExecutor. với số lượng chủ đề tối đa được thiết lập;


6

Các ThreadPoolExecutorlớp học là việc thực hiện cơ sở cho Chấp hành viên được trả về từ rất nhiều các Executorsphương pháp nhà máy. Vì vậy, hãy tiếp cận nhóm luồng cố địnhbộ đệmThreadPoolExecutor theo quan điểm của.

ThreadPoolExecutor

Hàm tạo chính của lớp này trông như thế này:

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

Kích thước bể bơi lõi

Việc corePoolSizexác định kích thước tối thiểu của nhóm luồng đích. Việc thực hiện sẽ duy trì một nhóm kích thước đó ngay cả khi không có nhiệm vụ nào để thực thi.

Kích thước bể bơi tối đa

Các maximumPoolSizelà số lượng tối đa của chủ đề mà có thể hoạt động cùng một lúc.

Sau khi nhóm luồng phát triển và trở nên lớn hơn corePoolSizengưỡng, người thực thi có thể chấm dứt các luồng không hoạt động và tiếp cận corePoolSizelại. Nếu allowCoreThreadTimeOutlà đúng, thì người thi hành thậm chí có thể chấm dứt các chuỗi nhóm lõi nếu chúng không hoạt động nhiều hơnkeepAliveTime ngưỡng.

Vì vậy, điểm mấu chốt là nếu các chủ đề vẫn nhàn rỗi hơn keepAliveTime ngưỡng, chúng có thể bị chấm dứt do không có nhu cầu cho chúng.

Xếp hàng

Điều gì xảy ra khi một nhiệm vụ mới xuất hiện và tất cả các luồng cốt lõi bị chiếm đóng? Các nhiệm vụ mới sẽ được xếp hàng bên trong đóBlockingQueue<Runnable> trường hợp . Khi một luồng trở nên miễn phí, một trong những tác vụ được xếp hàng đó có thể được xử lý.

Có các cách triển khai BlockingQueuegiao diện khác nhau trong Java, vì vậy chúng tôi có thể triển khai các cách tiếp cận xếp hàng khác nhau như:

  1. Hàng đợi bị ràng buộc : Các tác vụ mới sẽ được xếp hàng trong hàng đợi tác vụ bị chặn.

  2. Hàng đợi không giới hạn: Các tác vụ mới sẽ được xếp hàng trong hàng đợi tác vụ không giới hạn. Vì vậy, hàng đợi này có thể phát triển nhiều như kích thước heap cho phép.

  3. Bàn giao đồng bộ : Chúng tôi cũng có thể sử dụng SynchronousQueueđể xếp hàng các tác vụ mới. Trong trường hợp đó, khi xếp hàng một tác vụ mới, một luồng khác phải chờ cho tác vụ đó.

Trình công việc

Đây là cách ThreadPoolExecutorthực thi một nhiệm vụ mới:

  1. Nếu ít hơn các corePoolSizeluồng đang chạy, hãy thử bắt đầu một luồng mới với tác vụ đã cho là công việc đầu tiên.
  2. Mặt khác, nó cố gắng thực hiện nhiệm vụ mới bằng BlockingQueue#offerphương thức này. Các offerphương pháp sẽ không chặn nếu hàng đợi là đầy đủ và ngay lập tức trở về false.
  3. Nếu nó không xếp hàng tác vụ mới (tức là offertrả về false), thì nó sẽ cố gắng thêm một luồng mới vào nhóm luồng với tác vụ này là công việc đầu tiên của nó.
  4. Nếu nó không thêm chủ đề mới, thì bộ thực thi sẽ bị tắt hoặc bão hòa. Dù bằng cách nào, nhiệm vụ mới sẽ bị từ chối bằng cách sử dụng được cung cấp RejectedExecutionHandler.

Sự khác biệt chính giữa nhóm luồng cố định và bộ đệm lưu trữ theo ba yếu tố sau:

  1. Kích thước bể bơi lõi
  2. Kích thước bể bơi tối đa
  3. Xếp hàng
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Loại hồ bơi | Kích thước lõi | Kích thước tối đa | Chiến lược xếp hàng |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Đã sửa | n (cố định) | n (cố định) | Không giới hạn `LinkedBlockingQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Lưu trữ | 0 | Số nguyên.MAX_VALUE | `Đồng bộQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +


Đã sửa lỗi chủ đề


Đây là cách Excutors.newFixedThreadPool(n)hoạt động:

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

Bạn có thể thấy:

  • Kích thước nhóm chủ đề là cố định.
  • Nếu có nhu cầu cao, nó sẽ không tăng.
  • Nếu các chủ đề không hoạt động trong một thời gian, nó sẽ không co lại.
  • Giả sử tất cả các luồng đó được sử dụng với một số tác vụ dài hạn và tỷ lệ đến vẫn còn khá cao. Vì người thực thi đang sử dụng một hàng đợi không giới hạn, nó có thể tiêu tốn một phần rất lớn của đống. Không may mắn, chúng ta có thể trải nghiệm một OutOfMemoryError.

Khi nào tôi nên sử dụng cái này hay cái kia? Chiến lược nào tốt hơn về mặt sử dụng tài nguyên?

Nhóm luồng có kích thước cố định dường như là một ứng cử viên tốt khi chúng ta sẽ giới hạn số lượng tác vụ đồng thời cho mục đích quản lý tài nguyên .

Ví dụ: nếu chúng ta sẽ sử dụng một người thi hành để xử lý các yêu cầu máy chủ web, thì một người thi hành cố định có thể xử lý các cụm yêu cầu một cách hợp lý hơn.

Để quản lý tài nguyên tốt hơn nữa, chúng tôi khuyên bạn nên tạo một tùy chỉnh ThreadPoolExecutorvới BlockingQueue<T>triển khai giới hạn cùng với hợp lý RejectedExecutionHandler.


Bộ đệm chủ đề


Đây là cách Executors.newCachedThreadPool()hoạt động:

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

Bạn có thể thấy:

  • Nhóm chủ đề có thể phát triển từ chủ đề không đến Integer.MAX_VALUE. Thực tế, nhóm chủ đề là không giới hạn.
  • Nếu bất kỳ chủ đề nào không hoạt động trong hơn 1 phút, nó có thể bị chấm dứt. Vì vậy, pool có thể co lại nếu các chủ đề vẫn còn quá nhiều.
  • Nếu tất cả các luồng được phân bổ bị chiếm giữ trong khi một tác vụ mới xuất hiện, thì nó sẽ tạo ra một luồng mới, vì việc đưa ra một nhiệm vụ mới SynchronousQueueluôn luôn thất bại khi không có ai ở đầu bên kia chấp nhận nó!

Khi nào tôi nên sử dụng cái này hay cái kia? Chiến lược nào tốt hơn về mặt sử dụng tài nguyên?

Sử dụng nó khi bạn có rất nhiều nhiệm vụ chạy ngắn có thể dự đoán được.


5

Bạn phải sử dụng newCachedThreadPool chỉ khi bạn có các tác vụ không đồng bộ ngắn hạn như được nêu trong Javadoc, nếu bạn gửi các tác vụ mất nhiều thời gian hơn để xử lý, cuối cùng bạn sẽ tạo ra quá nhiều luồng. Bạn có thể đạt 100% CPU nếu bạn gửi các tác vụ chạy dài với tốc độ nhanh hơn tới newCachedThreadPool ( http://rashcoder.com/be-careful-fter-USE-executors-newcachedthreadpool/ ).


1

Tôi làm một số xét nghiệm nhanh và có những phát hiện sau:

1) nếu sử dụng SynousQueue:

Sau khi các chủ đề đạt kích thước tối đa, bất kỳ tác phẩm mới nào sẽ bị từ chối với ngoại lệ như dưới đây.

Ngoại lệ trong luồng "chính" java.util.concản.RejectionExecutException: Nhiệm vụ java.util.concản.FutureTask@3fee733d bị từ chối từ java.util.concản.ThreadPoolExecutor@5acf9800 [Chạy, kích thước nhóm = 3, hoạt động = 0, hoàn thành nhiệm vụ = 0]

tại java.util.conc hiện.ThreadPoolExecutor $ AbortPolicy.rejectionExecut (ThreadPoolExecutor.java:2047)

2) nếu sử dụng LinkedBlockingQueue:

Các luồng không bao giờ tăng từ kích thước tối thiểu đến kích thước tối đa, có nghĩa là nhóm luồng có kích thước cố định là kích thước tối thiểu.

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.