Làm thế nào để sử dụng ConcurrentLinkedQueue?


95

Làm cách nào để sử dụng một ConcurrentLinkedQueuetrong Java?
Sử dụng điều này LinkedQueue, tôi có cần phải lo lắng về sự đồng thời trong hàng đợi không? Hay tôi chỉ phải xác định hai phương thức (một để lấy lại các phần tử từ danh sách và một phương thức khác để thêm các phần tử vào danh sách)?
Lưu ý: rõ ràng hai phương thức này phải được đồng bộ hóa. Đúng?


CHỈNH SỬA: Điều tôi đang cố gắng thực hiện là: Tôi có một lớp (trong Java) với một phương thức để lấy các mục từ hàng đợi và một lớp khác có một phương thức để thêm các mục vào hàng đợi. Các mục được thêm vào và truy xuất từ ​​danh sách là các đối tượng của lớp tôi.

Một câu hỏi nữa: tôi có cần thực hiện việc này trong phương thức remove không:

while (queue.size() == 0){ 
  wait(); 
  queue.poll();
}

Tôi chỉ có một người tiêu dùng và một nhà sản xuất.


Cảm ơn vì đã trả lời câu hỏi mt. Điều tôi đang cố gắng làm là thế này: tôi có một lớp (trong Java) với một phương thức để lấy các mục từ hàng đợi và một lớp khác có một phương thức để thêm các mục vào hàng đợi. Các mục được thêm vào và truy xuất từ ​​danh sách là các đối tượng của lớp tôi.
Ricardo Felgueiras

2
Bạn nên chỉnh sửa câu hỏi của mình và đưa phần làm rõ này vào chính câu hỏi.
Adam Jaskiewicz

Câu trả lời:


156

Không, các phương thức không cần đồng bộ và bạn không cần xác định bất kỳ phương thức nào; chúng đã có trong ConcurrentLinkedQueue, chỉ cần sử dụng chúng. ConcurrentLinkedQueue thực hiện tất cả các thao tác khóa và các thao tác khác mà bạn cần trong nội bộ; (các) nhà sản xuất của bạn thêm dữ liệu vào hàng đợi và người tiêu dùng của bạn thăm dò ý kiến ​​đó.

Đầu tiên, hãy tạo hàng đợi của bạn:

Queue<YourObject> queue = new ConcurrentLinkedQueue<YourObject>();

Bây giờ, bất cứ nơi nào bạn đang tạo các đối tượng nhà sản xuất / người tiêu dùng của mình, hãy chuyển vào hàng đợi để họ có một nơi nào đó để đặt các đối tượng của mình (thay vào đó, bạn có thể sử dụng một setter cho việc này, nhưng tôi thích làm kiểu này hơn trong một constructor):

YourProducer producer = new YourProducer(queue);

và:

YourConsumer consumer = new YourConsumer(queue);

và thêm nội dung vào đó trong trình sản xuất của bạn:

queue.offer(myObject);

và lấy những thứ trong người tiêu dùng của bạn (nếu hàng đợi trống, thăm dò ý kiến ​​() sẽ trả về null, vì vậy hãy kiểm tra nó):

YourObject myObject = queue.poll();

Để biết thêm thông tin, hãy xem Javadoc

BIÊN TẬP:

Nếu bạn cần chặn chờ đợi hàng đợi không bị trống, bạn có thể muốn sử dụng LinkedBlockingQueue và sử dụng phương thức take (). Tuy nhiên, LinkedBlockingQueue có dung lượng tối đa (mặc định là Integer.MAX_VALUE, là hơn hai tỷ) và do đó có thể thích hợp hoặc không tùy thuộc vào hoàn cảnh của bạn.

Nếu bạn chỉ có một luồng đưa thứ vào hàng đợi và một luồng khác đưa thứ ra khỏi hàng đợi, ConcurrentLinkedQueue có thể là quá mức cần thiết. Nó nhiều hơn khi bạn có thể có hàng trăm hoặc thậm chí hàng nghìn luồng truy cập hàng đợi cùng một lúc. Nhu cầu của bạn có thể sẽ được đáp ứng bằng cách sử dụng:

Queue<YourObject> queue = Collections.synchronizedList(new LinkedList<YourObject>());

Một điểm cộng của điều này là nó khóa trên cá thể (hàng đợi), vì vậy bạn có thể đồng bộ hóa trên hàng đợi để đảm bảo tính nguyên tử của các hoạt động tổng hợp (như Jared giải thích). Bạn KHÔNG THỂ làm điều này với ConcurrentLinkedQueue, vì tất cả các hoạt động được thực hiện mà KHÔNG khóa trên phiên bản (sử dụng các biến java.util.concurrent.atomic). Bạn sẽ KHÔNG cần phải làm điều này nếu bạn muốn chặn khi hàng đợi trống, bởi vì thăm dò ý kiến ​​() sẽ chỉ trả về null trong khi hàng đợi trống và thăm dò ý kiến ​​() là nguyên tử. Kiểm tra xem thăm dò ý kiến ​​() trả về null. Nếu có, hãy đợi (), sau đó thử lại. Không cần khóa.

Cuối cùng:

Thành thật mà nói, tôi chỉ muốn sử dụng một LinkedBlockingQueue. Nó vẫn còn quá mức cần thiết cho ứng dụng của bạn, nhưng tỷ lệ cược là nó sẽ hoạt động tốt. Nếu nó không đủ hiệu quả (PROFILE!), Bạn luôn có thể thử một cái gì đó khác và điều đó có nghĩa là bạn không phải xử lý BẤT KỲ nội dung đồng bộ nào:

BlockingQueue<YourObject> queue = new LinkedBlockingQueue<YourObject>();

queue.put(myObject); // Blocks until queue isn't full.

YourObject myObject = queue.take(); // Blocks until queue isn't empty.

Mọi thứ khác là như nhau. Đặt có thể sẽ không chặn, bởi vì bạn không có khả năng đưa hai tỷ đối tượng vào hàng đợi.


Cám ơn phản hồi của bạn. Một câu hỏi nữa: tôi có cần thực hiện việc này trong phương thức remove: while (queue.size () == 0) wait (); queue.poll ();
Ricardo Felgueiras

Tôi sẽ trả lời điều đó dưới dạng chỉnh sửa cho câu trả lời của tôi, vì nó khá quan trọng.
Adam Jaskiewicz

Như đã gây ra nhầm lẫn trong một câu hỏi khác, Collection.synchronizedListtrả về a Listkhông triển khai Queue.
Tom Hawtin - chạm bóng vào

@AdamJaskiewicz đang sử dụng ConcurrentLinkedQueuecho sản xuất tiêu dùng là một ý tưởng tốt, tôi đề cập đến bài này stackoverflow.com/questions/1426754/...
rd22

37

Đây phần lớn là một bản sao của một câu hỏi khác .

Đây là phần câu trả lời có liên quan đến câu hỏi này:

Tôi có cần thực hiện đồng bộ hóa của riêng mình nếu tôi sử dụng java.util.ConcurrentLinkedQueue không?

Các hoạt động nguyên tử trên các tập hợp đồng thời được đồng bộ hóa cho bạn. Nói cách khác, mỗi lệnh gọi riêng lẻ đến hàng đợi được đảm bảo an toàn theo chuỗi mà không cần bất kỳ hành động nào từ phía bạn. Điều không được đảm bảo an toàn cho luồng là bất kỳ hoạt động nào bạn thực hiện trên bộ sưu tập không phải là nguyên tử.

Ví dụ: đây là threadsafe mà không có bất kỳ hành động nào từ phía bạn:

queue.add(obj);

hoặc là

queue.poll(obj);

Tuy nhiên; các lệnh gọi không phải nguyên tử đến hàng đợi không tự động an toàn theo luồng. Ví dụ: các hoạt động sau không tự động an toàn luồng:

if(!queue.isEmpty()) {
   queue.poll(obj);
}

Điều cuối cùng đó không phải là threadsafe, vì rất có thể giữa thời gian isEmpty được gọi và cuộc thăm dò thời gian được gọi, các luồng khác sẽ thêm hoặc xóa các mục khỏi hàng đợi. Cách threadsafe để thực hiện điều này như sau:

synchronized(queue) {
    if(!queue.isEmpty()) {
       queue.poll(obj);
    }
}

Một lần nữa ... các lệnh gọi nguyên tử đến hàng đợi sẽ tự động an toàn theo luồng. Các cuộc gọi phi nguyên tử thì không.


1
Cách sử dụng phổ biến duy nhất mà tôi có thể làm là chặn cho đến khi hàng đợi không còn trống. Điều đó sẽ không được phục vụ tốt hơn bằng cách sử dụng việc triển khai BlockingQueue (take () là nguyên tử và các khối cho đến khi có thứ gì đó để tiêu thụ)?
Adam Jaskiewicz

6
Ngoài ra bạn cần phải cẩn thận với điều đó. Không giống như syncList, ConcurrentLinkedQueue không tự đồng bộ hóa, vì vậy trong mã của bạn, nhà sản xuất vẫn có thể cung cấp cho hàng đợi khi bạn đang ở trong khối được đồng bộ hóa của mình.
Adam Jaskiewicz

Tại sao bạn lại kết hợp queue.isEmpty () và queue.poll ()? Bạn không thể chỉ thăm dò và kiểm tra xem kết quả có là rỗng không? (hiểu biết của tôi là nếu bạn thăm dò ý kiến một hàng đợi rỗng nó sẽ trả về null)
AjahnCharles

Tôi đồng ý với mọi thứ trong mã của bạn ngoại trừ khối mã cuối cùng. Đồng bộ hóa trên ConcurrentLInkedQueue không đảm bảo cho bạn bất cứ điều gì vì các cuộc gọi khác không được đồng bộ hóa. Vì vậy, có thể có "thêm (..)" xảy ra từ một chuỗi khác giữa "isEmpty ()" và "thăm dò ý kiến ​​(...)" của bạn ngay cả khi bạn đã đồng bộ hóa chuỗi này
Klitos G.

6

Sử dụng thăm dò ý kiến để lấy phần tử đầu tiên và thêm để thêm phần tử cuối cùng mới. Đó là nó, không có đồng bộ hóa hoặc bất cứ điều gì khác.


6

Đây có thể là những gì bạn đang tìm kiếm về độ an toàn và "độ đẹp" của luồng khi cố gắng tiêu thụ mọi thứ trong hàng đợi:

for (YourObject obj = queue.poll(); obj != null; obj = queue.poll()) {
}

Điều này sẽ đảm bảo rằng bạn thoát khi hàng đợi trống và bạn tiếp tục bật các đối tượng ra khỏi nó miễn là nó không trống.


Rất hữu ích để làm trống hàng đợi. Cảm ơn.
DevilCode

2

ConcurentLinkedQueue là một triển khai miễn phí chờ / khóa rất hiệu quả (xem javadoc để tham khảo), vì vậy bạn không chỉ không cần đồng bộ hóa mà hàng đợi sẽ không khóa bất cứ thứ gì, do đó gần như nhanh như không đồng bộ hóa (không phải luồng an toàn) một.


1

Chỉ cần sử dụng nó như bạn làm với một bộ sưu tập không đồng thời. Các lớp [Bộ sưu tập] đồng thời bao bọc các bộ sưu tập thông thường để bạn không phải suy nghĩ về việc đồng bộ hóa quyền truy cập.

Chỉnh sửa: ConcurrentLinkedList thực sự không chỉ là một trình bao bọc mà còn là một triển khai đồng thời tốt hơn. Dù bằng cách nào, bạn không phải lo lắng về việc đồng bộ hóa.


ConcurrentLinkedQueue thì không. Nó được xây dựng đặc biệt ngay từ đầu để nhiều nhà sản xuất và người tiêu dùng truy cập đồng thời. Một fancier chút so với giấy gói đơn giản trả về bởi Collections.synchronized *
Adam Jaskiewicz

Vâng, có rất nhiều thứ đồng thời gọn gàng thêm vào trong J2SE 5.
Adam Jaskiewicz
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.