newCachedThreadPool()
đấu với newFixedThreadPool()
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?
newCachedThreadPool()
đấu với newFixedThreadPool()
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?
Câu trả lời:
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:
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.
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, newFixedThreadPool
sẽ 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 newCachedThreadPool
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.
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".
newCachedThreadPool
có 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 pool
và 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 newFixedThreadPool
có 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.
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. "
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>());
}
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 newFixedThreadPool
hoặcnewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Ưu điểm:
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.
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:
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.
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.
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ỏ.
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.)
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:
Đ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ó:
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).
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;
Các ThreadPoolExecutor
lớ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 Executors
phương pháp nhà máy. Vì vậy, hãy tiếp cận nhóm luồng cố định và bộ đệmThreadPoolExecutor
theo quan điểm của.
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
)
Việc corePoolSize
xá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.
Các maximumPoolSize
là 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 corePoolSize
ngưỡ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 corePoolSize
lại. Nếu allowCoreThreadTimeOut
là đú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.
Đ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 BlockingQueue
giao 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ư:
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.
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.
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ụ đó.
Đây là cách ThreadPoolExecutor
thực thi một nhiệm vụ mới:
corePoolSize
luồ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.BlockingQueue#offer
phương thức này. Các offer
phương pháp sẽ không chặn nếu hàng đợi là đầy đủ và ngay lập tức trở về false
.offer
trả 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ó.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:
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | 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` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
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:
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 ThreadPoolExecutor
với BlockingQueue<T>
triển khai giới hạn cùng với hợp lý RejectedExecutionHandler
.
Đâ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:
Integer.MAX_VALUE
. Thực tế, nhóm chủ đề là không giới hạn.SynchronousQueue
luô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.
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/ ).
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.