LinkedBlockingQueue so với ConcurrentLinkedQueue


112

Câu hỏi của tôi liên quan đến câu hỏi này đã hỏi trước đó. Trong các tình huống mà tôi đang sử dụng hàng đợi để giao tiếp giữa các chủ đề của nhà sản xuất và người tiêu dùng, mọi người thường khuyên bạn nên sử dụng LinkedBlockingQueuehay ConcurrentLinkedQueue?

Ưu điểm / nhược điểm của việc sử dụng cái này so với cái kia là gì?

Sự khác biệt chính mà tôi có thể thấy từ góc độ API là a LinkedBlockingQueuecó thể được giới hạn tùy chọn.

Câu trả lời:


110

Đối với chuỗi nhà sản xuất / người tiêu dùng, tôi không chắc đó có phải ConcurrentLinkedQueuelà một lựa chọn hợp lý hay không - nó không triển khai BlockingQueue, đó là giao diện cơ bản cho hàng đợi nhà sản xuất / người tiêu dùng IMO. Bạn sẽ phải gọi điện poll(), đợi một chút nếu bạn không tìm thấy gì, và sau đó thăm dò lại, v.v. .

Từ các tài liệu cho BlockingQueue:

BlockingQueue triển khai được thiết kế để sử dụng chủ yếu cho hàng đợi của nhà sản xuất-người tiêu dùng

Tôi biết nó không hoàn toàn nói rằng chỉ nên sử dụng hàng đợi chặn cho hàng đợi của nhà sản xuất-người tiêu dùng, nhưng ngay cả như vậy ...


4
Cảm ơn Jon - Tôi đã không nhận thấy điều đó. Vậy ở đâu / tại sao bạn sẽ sử dụng ConcurrentLinkedQueue?
Adamski 15/09/09

27
Khi bạn cần truy cập hàng đợi từ nhiều luồng, nhưng bạn không cần phải "chờ đợi" trên đó.
Jon Skeet 15/09/09

2
A ConcurrentLinkedQueuecũng hữu ích nếu luồng của bạn đang kiểm tra nhiều hàng đợi. Ví dụ: trong một máy chủ nhiều người thuê. Giả sử vì lý do cô lập, thay vào đó bạn không sử dụng một hàng đợi chặn duy nhất và một bộ phân biệt đối tượng thuê.
BênFractal

trường hợp của bạn chỉ đúng nếu chúng tôi sử dụng hàng đợi có giới hạn , trong hàng đợi không bị giới hạntake()put()chỉ tiêu tốn thêm tài nguyên (các khoảng đồng bộ hóa) hơn ConcurrentLinkedQueue . mặc dù trường hợp sử dụng hàng đợi có giới hạn cho các kịch bản Nhà sản xuất-người tiêu dùng
amarnath harish

@Adamski IMO, ConcurrentLinkedQueue chỉ là một danh sách liên kết được sử dụng trong môi trường đa luồng. Tương tự tốt nhất cho điều này sẽ là ConcurrentHashMap và HashMap.
Nishit

69

Câu hỏi này xứng đáng có một câu trả lời tốt hơn.

Java's ConcurrentLinkedQueuedựa trên thuật toán nổi tiếng của Maged M. Michael và Michael L. Scott cho các hàng đợi không khóa không khóa .

"Không chặn" như một thuật ngữ ở đây cho một tài nguyên cạnh tranh (hàng đợi của chúng tôi) có nghĩa là bất kể trình lập lịch của nền tảng làm gì, như làm gián đoạn một chuỗi hoặc nếu chuỗi được đề cập đơn giản là quá chậm, các chuỗi khác sẽ tranh giành cùng một tài nguyên vẫn sẽ có thể tiến triển. Ví dụ: nếu liên quan đến một khóa, chuỗi đang giữ khóa có thể bị gián đoạn và tất cả các chuỗi đang chờ khóa đó sẽ bị chặn. Khóa nội tại ( synchronizedtừ khóa) trong Java cũng có thể đi kèm với một hình phạt nghiêm trọng cho hiệu suất - như khi khóa thiên vịcó liên quan và bạn có tranh chấp hoặc sau khi máy ảo quyết định "thổi phồng" khóa sau thời gian gia hạn quay vòng và chặn các chủ đề cạnh tranh ... đó là lý do tại sao trong nhiều bối cảnh (tình huống tranh chấp thấp / trung bình), thực hiện so sánh-và -sets trên tham chiếu nguyên tử có thể hiệu quả hơn nhiều và đây chính xác là điều mà nhiều cấu trúc dữ liệu không chặn đang làm.

Java ConcurrentLinkedQueuekhông chỉ không bị chặn, mà còn có đặc tính tuyệt vời mà nhà sản xuất không cạnh tranh với người tiêu dùng. Trong kịch bản một nhà sản xuất / một người tiêu dùng duy nhất (SPSC), điều này thực sự có nghĩa là sẽ không có gì phải bàn cãi. Trong một kịch bản nhiều nhà sản xuất / một người tiêu dùng, người tiêu dùng sẽ không cạnh tranh với các nhà sản xuất. Hàng đợi này không có sự tranh chấp khi nhiều nhà sản xuất cố gắng offer(), nhưng đó là đồng thời theo định nghĩa. Về cơ bản, đó là một mục đích chung và hàng đợi không chặn hiệu quả.

Đối với nó không phải là một BlockingQueue, tốt, chặn một luồng để chờ trên hàng đợi là một cách khủng khiếp khủng khiếp để thiết kế các hệ thống đồng thời. Đừng. Nếu bạn không thể tìm ra cách sử dụng một ConcurrentLinkedQueuekịch bản dành cho người tiêu dùng / nhà sản xuất, thì chỉ cần chuyển sang các nội dung trừu tượng cấp cao hơn, như một khuôn khổ tác nhân tốt.


8
Theo đoạn cuối cùng của bạn, tại sao bạn lại nói rằng chờ đợi trên hàng đợi là một cách tồi tệ để thiết kế các hệ thống đồng thời? Nếu chúng ta có một nhóm luồng với 10 chủ đề ăn nhiệm vụ từ một hàng tác vụ, thì có gì sai khi chặn khi hàng tác vụ có ít hơn 10 tác vụ?
Pacerier

11
@AlexandruNedelcu Bạn không thể đưa ra một tuyên bố bao quát như "khủng khiếp một cách kỳ lạ" trong đó rất thường xuyên các khuôn khổ tác nhân mà bạn nói là sử dụng các threadpools mà chính bạn là BlockingQueue . Nếu bạn cần một hệ thống phản ứng cao và bạn biết cách đối phó với áp suất ngược (thứ mà việc chặn hàng đợi giảm thiểu) hơn là không chặn rõ ràng là ưu việt hơn. Nhưng .. thường thì việc chặn IO và hàng đợi chặn có thể thực hiện không chặn, đặc biệt nếu bạn có các nhiệm vụ chạy dài bị ràng buộc IO và không thể chia n 'được.
Adam Gent

1
@AdamGent - Các khung tác nhân có triển khai các hộp thư dựa trên hàng đợi chặn, nhưng đó là một lỗi theo ý kiến ​​của tôi, vì tính năng chặn không hoạt động trên các ranh giới không đồng bộ và do đó chỉ hoạt động trong các bản trình diễn. Đối với tôi, điều này là một nguồn gốc của sự thất vọng, chẳng hạn như quan niệm của Akka về việc xử lý tràn là chặn, thay vì thả tin nhắn, cho đến khi phiên bản 2.4 vẫn chưa ra mắt. Điều đó nói rằng tôi không tin rằng có những trường hợp sử dụng mà hàng đợi chặn có thể vượt trội hơn. Bạn cũng đang phân tích hai điều không nên kết hợp. Tôi chưa nói về việc chặn I / O.
Alexandru Nedelcu

1
@AlexandruNedelcu trong khi tôi thường đồng ý với bạn về áp suất ngược, tôi vẫn chưa thấy hệ thống "khóa miễn phí" từ trên xuống dưới. Ở đâu đó trong một ngăn xếp công nghệ cho dù Node.js, Erlang, Golang của nó, nó sử dụng một số loại chiến lược chờ đợi cho dù đó là một hàng đợi (ổ khóa) hay CAS quay vòng chặn và một số trường hợp, chiến lược khóa truyền thống nhanh hơn. Rất khó để không có khóa vì tính nhất quán và điều này đặc biệt quan trọng với việc chặn io và bộ lập lịch là ~ Nhà sản xuất / Người tiêu dùng. ForkJoinPool hoạt động với các tác vụ chạy ngắn và nó vẫn có khóa quay CAS.
Adam Gent

1
@AlexandruNedelcu Tôi đoán nếu bạn có thể chỉ cho tôi cách bạn có thể sử dụng ConcurrentLinkedQueue (không bị ràng buộc btw do đó đối số áp suất ngược yếu của tôi) cho mẫu Nhà sản xuất / Người tiêu dùng, đây là một mẫu cần thiết cho người lập lịch và chia sẻ luồng, tôi nghĩ tôi sẽ nhượng bộ và thừa nhận rằng BlockingQueue's không bao giờ nên được sử dụng (và bạn không thể gian lận và ủy quyền cho người khác thực hiện việc lập lịch, tức là akka vì điều đó sẽ thực hiện việc chặn / chờ đợi vì nó là nhà sản xuất / người tiêu dùng).
Adam Gent

33

LinkedBlockingQueuechặn người tiêu dùng hoặc nhà sản xuất khi hàng đợi trống hoặc đầy và luồng người tiêu dùng / nhà sản xuất tương ứng được chuyển sang trạng thái ngủ. Nhưng tính năng ngăn chặn này đi kèm với một cái giá phải trả: mọi hoạt động bán hoặc nhận đều có sự cạnh tranh giữa người sản xuất hoặc người tiêu dùng (nếu nhiều người), vì vậy trong các tình huống có nhiều nhà sản xuất / người tiêu dùng, hoạt động có thể chậm hơn.

ConcurrentLinkedQueuekhông sử dụng khóa, nhưng CAS , trong hoạt động bán / nhận của mình có khả năng làm giảm sự cạnh tranh với nhiều nhà sản xuất và người tiêu dùng. Nhưng là một cấu trúc dữ liệu "đợi miễn phí", ConcurrentLinkedQueuesẽ không chặn khi trống, có nghĩa là người tiêu dùng sẽ cần phải xử lý các giá trị take()trả về nullbằng cách "bận chờ đợi", ví dụ, với luồng tiêu dùng ăn hết CPU.

Vì vậy, cái nào là "tốt hơn" phụ thuộc vào số lượng chủ đề của người tiêu dùng, vào tốc độ họ tiêu thụ / sản xuất, v.v. Cần có một điểm chuẩn cho mỗi tình huống.

Một trường hợp sử dụng cụ thể ConcurrentLinkedQueuerõ ràng là tốt hơn khi người sản xuất lần đầu tiên sản xuất một thứ gì đó và hoàn thành công việc của họ bằng cách đặt sản phẩm vào hàng đợi và chỉ sau khi người tiêu dùng bắt đầu tiêu thụ, biết rằng chúng sẽ được thực hiện khi hàng đợi trống. (ở đây không có sự đồng thời giữa người sản xuất và người tiêu dùng mà chỉ giữa người sản xuất-người sản xuất và người tiêu dùng-người tiêu dùng)


một nghi ngờ ở đây. Khi bạn thứ hai người tiêu dùng chờ đợi khi hàng đợi trống ... nó chờ bao lâu. Ai sẽ thông báo cho nó để không phải chờ đợi?
Brinal

@brindal Cách duy nhất để nó đợi, mà tôi biết, là trong một vòng lặp. Đó là một vấn đề quan trọng chưa được chú ý nhiều trong các câu trả lời ở đây. Chỉ chạy một vòng lặp chờ dữ liệu sử dụng rất nhiều thời gian của bộ xử lý. Bạn sẽ biết điều đó khi người hâm mộ của bạn bắt đầu quay vòng. Cách khắc phục duy nhất là đặt một giấc ngủ trong vòng lặp. Vì vậy, đó là một vấn đề trong một hệ thống với luồng dữ liệu không nhất quán. Có lẽ tôi hiểu sai câu trả lời của AlexandruNedelcu, nhưng bản thân một hệ điều hành là một hệ thống đồng thời, sẽ cực kỳ kém hiệu quả nếu nó chứa đầy các vòng lặp sự kiện không chặn.
orodbhen

okay, nhưng nếu unbounded blockingqueueđược sử dụng nó sẽ là tốt hơn so với CAS dựa đồng thờiConcurrentLinkedQueue
Amarnath Harish

@orodbhen Ngủ một giấc cũng không giúp loại bỏ lãng phí. Hệ điều hành phải thực hiện rất nhiều công việc để đưa một chuỗi ra khỏi trạng thái ngủ và lên lịch cũng như chạy nó. Nếu thông báo chưa có sẵn, công việc do hệ điều hành của bạn thực hiện sẽ bị lãng phí. Tôi khuyên bạn tốt hơn nên sử dụng BlockingQueue, vì nó được thiết kế đặc biệt cho vấn đề nhà sản xuất-người tiêu dùng.
Nishit

thực ra, tôi rất quan tâm đến phần "tỷ lệ tiêu thụ / sản xuất", vậy cái nào tốt hơn nếu tỷ lệ tăng cao?
workplaylifecycle

0

Một giải pháp khác (không mở rộng quy mô tốt) là các kênh điểm hẹn: java.util.concurrent SynchronousQueue


0

Nếu hàng đợi của bạn không thể mở rộng và chỉ chứa một chuỗi nhà sản xuất / người tiêu dùng. Bạn có thể sử dụng hàng đợi không khóa (Bạn không cần phải khóa quyền truy cập dữ liệ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.