2009-07-31 47 views
26

Tôi có vấn đề cổ điển về sự kiện đẩy luồng tới hàng đợi đến của chuỗi thứ hai. Chỉ có thời gian này, tôi rất quan tâm đến hiệu suất. Những gì tôi muốn đạt được là:Hàng đợi đồng thời và chặn trong Java

  • Tôi muốn truy cập đồng thời vào hàng đợi, nhà sản xuất đẩy, người nhận bật lên.
  • Khi hàng đợi trống, tôi muốn người tiêu dùng chặn hàng đợi, đợi nhà sản xuất.

Ý tưởng đầu tiên của tôi là sử dụng LinkedBlockingQueue, nhưng tôi sớm nhận ra rằng nó không đồng thời và hiệu suất bị ảnh hưởng. Mặt khác, bây giờ tôi sử dụng một số ConcurrentLinkedQueue, nhưng tôi vẫn phải trả chi phí wait()/notify() trên mỗi ấn phẩm. Vì người tiêu dùng, khi tìm hàng trống, không chặn, tôi phải đồng bộ hóa và wait() trên khóa. Mặt khác, nhà sản xuất phải lấy khóa đó và notify() sau mỗi lần xuất bản. Kết quả tổng thể là tôi đang trả chi phí sycnhronized (lock) {lock.notify()} trong mọi ấn bản duy nhất, ngay cả khi không cần thiết.

Điều tôi đoán là cần thiết ở đây, là hàng đợi vừa chặn vừa đồng thời. Tôi hình dung hoạt động của push() hoạt động như trong ConcurrentLinkedQueue, với thêm notify() đối tượng khi phần tử được đẩy là phần tử đầu tiên trong danh sách. Một kiểm tra như vậy tôi coi là đã tồn tại trong ConcurrentLinkedQueue, vì việc đẩy yêu cầu kết nối với phần tử tiếp theo. Do đó, điều này sẽ nhanh hơn nhiều so với đồng bộ hóa mọi lúc trên khóa ngoài.

Có điều gì đó như thế này có sẵn/hợp lý không?

+0

Tại sao bạn nghĩ rằng java.util.concurrent.LinkedBlockingQueue không đồng thời? Tôi nghĩ rằng nó là hoàn toàn đồng thời, nhìn thấy javadoc của nó và mã nguồn. Nhưng không có ý tưởng về hiệu suất. – Rorick

+0

Xem thêm http://stackoverflow.com/questions/1301691/java-queue-implementations-which-one – Vadzim

Trả lời

11

Tôi nghĩ bạn có thể gắn bó với java.util.concurrent.LinkedBlockingQueue bất kể nghi ngờ của bạn. Nó là đồng thời. Mặc dù, tôi không có ý tưởng về hiệu suất của nó. Có thể, việc triển khai khác của BlockingQueue sẽ phù hợp với bạn tốt hơn. Không có quá nhiều người trong số họ, vì vậy hãy kiểm tra và đo lường hiệu suất.

+0

Tôi nói điều này chỉ bằng cách quan sát thông lượng của tôi giảm khoảng 8 lần so với ConcurrentLinkedQueue. Tôi đoán nó đã được khóa toàn bộ điều nội bộ chỉ để cung cấp an toàn thread. Bạn có quyền mặc dù, nó cũng có thể chỉ là hiệu suất tồi tệ hơn so với ConcurrentLinkedQueue. Những loại nhịp đập gây ra ;-) – Yiannis

+1

Bao giờ hàng đợi bạn sử dụng, bạn sẽ kết thúc bằng cách sử dụng một khóa để hỗ trợ chờ đợi cho một mục mới. (trừ khi bạn bận rộn chờ đợi) Khóa thực sự không phải là đắt tiền (khoảng 0,5 micro giây đối với Khóa) vì vậy nếu vấn đề về hiệu suất của nó, bạn có thể gặp sự cố với thiết kế của mình, ví dụ: tạo ít tác vụ/tìm cách thêm ít đối tượng hơn vào hàng đợi/mẻ lên công việc của bạn. –

+6

Lưu ý, nếu bạn muốn thông lượng cao hơn, sử dụng drainTo() trên LinkedBlcokingQueue của bạn, chúng tôi đã đo được gần 500% thông lượng trên một trong các ứng dụng của chúng tôi, thay vì lấy() một phần tử ra khỏi hàng đợi – nos

2

Đây là list of classes implementing BlockingQueue.

Tôi khuyên bạn nên kiểm tra ra SynchronousQueue.

Giống như @Rorick được đề cập trong nhận xét của anh ấy, tôi tin rằng tất cả những triển khai đó đều đồng thời. Tôi nghĩ rằng mối quan tâm của bạn với LinkedBlockingQueue có thể không đúng chỗ.

+1

Đồng bộQueue không phải là điều tôi muốn, vì nó sẽ chặn nhà sản xuất của tôi mỗi khi nhà xuất bản cố gắng xuất bản. – Yiannis

+0

SynchronousQueue trông giống một đường ống hơn là xếp hàng. Có vẻ như nó không thể chứa các tác vụ đang chờ xử lý trong đó, chỉ "đẩy" các tác vụ đơn lẻ từ nhà sản xuất đến người tiêu dùng. – Rorick

+0

Hehe, xin lỗi. – jjnguy

5

Tôi khuyên bạn nên xem ThreadPoolExecutor newSingleThreadExecutor. Nó sẽ xử lý việc giữ nhiệm vụ của bạn được đặt hàng cho bạn và nếu bạn gửi Callables cho người thực thi của mình, bạn cũng sẽ có thể nhận được hành vi chặn mà bạn đang tìm kiếm.

3

Tôi sử dụng ArrayBlockingQueue bất cứ khi nào tôi cần chuyển dữ liệu từ chuỗi này sang chuỗi khác. Sử dụng các phương thức đặt và lấy (sẽ chặn nếu đầy/trống).

4

Bạn có thể thử LinkedTransferQueue từ jsr166: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166y/

Nó đáp ứng yêu cầu của bạn và có ít overhead cho các hoạt động phục vụ/thăm dò. Như tôi thấy từ mã, khi hàng đợi không trống, nó sử dụng các hoạt động nguyên tử cho các phần tử bỏ phiếu. Và khi hàng đợi trống, nó sẽ quay trong một thời gian và đỗ chuỗi nếu không thành công. Tôi nghĩ rằng nó có thể giúp đỡ trong trường hợp của bạn.

6

Giống như câu trả lời này https://stackoverflow.com/a/1212515/1102730 nhưng có một chút khác biệt .. Tôi đã sử dụng một số ExecutorService. Bạn có thể khởi tạo một bằng cách sử dụng Executors.newSingleThreadExecutor(). Tôi cần một hàng đợi đồng thời để đọc/ghi BufferedImages vào các tệp, cũng như atomicity với các lần đọc và viết. Tôi chỉ cần một chủ đề duy nhất bởi vì các tập tin IO là đơn đặt hàng của cường độ nhanh hơn so với nguồn, net IO. Ngoài ra, tôi đã quan tâm nhiều hơn về nguyên tử của các hành động và tính chính xác hơn hiệu suất, nhưng cách tiếp cận này cũng có thể được thực hiện với nhiều chủ đề trong hồ bơi để tăng tốc độ mọi thứ.

Để có được một hình ảnh (Try-Catch-Cuối cùng bỏ qua):

Future<BufferedImage> futureImage = executorService.submit(new Callable<BufferedImage>() { 
    @Override 
     public BufferedImage call() throws Exception { 
      ImageInputStream is = new FileImageInputStream(file); 
      return ImageIO.read(is); 
     } 
    }) 

image = futureImage.get(); 

Để lưu một hình ảnh (Try-Catch-Cuối cùng bỏ qua):

Future<Boolean> futureWrite = executorService.submit(new Callable<Boolean>() { 
    @Override 
    public Boolean call() { 
     FileOutputStream os = new FileOutputStream(file); 
     return ImageIO.write(image, getFileFormat(), os); 
    } 
}); 

boolean wasWritten = futureWrite.get(); 

Điều quan trọng cần lưu ý là bạn nên tuôn ra và đóng luồng của bạn trong một khối cuối cùng. Tôi không biết làm thế nào nó thực hiện so với các giải pháp khác, nhưng nó là khá linh hoạt.

Các vấn đề liên quan