2010-06-01 31 views
48

Tôi đã xem xét OpenJDK source code của CopyOnWriteArrayList và có vẻ như tất cả các hoạt động ghi được bảo vệ bởi cùng một khóa và hoạt động đọc không được bảo vệ. Theo tôi hiểu, dưới JMM tất cả các truy cập vào một biến (cả đọc và viết) nên được bảo vệ bằng khóa hoặc hiệu ứng sắp xếp lại có thể xảy ra.Làm thế nào để CopyOnWriteArrayList có thể an toàn luồng?

Ví dụ, set(int, E) phương pháp chứa những dòng này (trong tủ có khoá):

/* 1 */ int len = elements.length; 
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len); 
/* 3 */ newElements[index] = element; 
/* 4 */ setArray(newElements); 

Phương pháp get(int), mặt khác, chỉ làm return get(getArray(), index);.

Theo hiểu biết của tôi về JMM, điều này có nghĩa là get có thể quan sát mảng trong trạng thái không nhất quán nếu câu 1-4 được sắp xếp lại như 1-2 (mới) -4-2 (copyOf) -3.

Tôi có hiểu JMM không chính xác hoặc có bất kỳ giải thích nào khác về lý do tại sao CopyOnWriteArrayList có an toàn không?

Trả lời

61

Nếu bạn nhìn vào tham chiếu mảng cơ bản, bạn sẽ thấy nó được đánh dấu là volatile. Khi hoạt động ghi xảy ra (chẳng hạn như trong phần trích dẫn ở trên), tham chiếu volatile này chỉ được cập nhật trong tuyên bố cuối cùng qua setArray. Cho đến thời điểm này, mọi thao tác đọc sẽ trả về các phần tử từ bản sao cũ của mảng đó.

Điểm quan trọng là cập nhật mảng là hoạt động nguyên tử và do đó lần đọc sẽ luôn thấy mảng ở trạng thái nhất quán.

Lợi thế của việc lấy ra khóa cho thao tác ghi được cải thiện thông lượng cho lần đọc: Điều này là do thao tác ghi cho CopyOnWriteArrayList có thể rất chậm khi chúng liên quan đến việc sao chép toàn bộ danh sách.

+0

Cảm ơn bạn. Tôi đã bỏ lỡ một thực tế là mảng là 'dễ bay hơi'. – Fixpoint

+15

Một chi tiết quan trọng là dễ bay hơi chỉ áp dụng cho tham chiếu mảng, chứ không áp dụng cho nội dung của mảng. Tuy nhiên, vì tất cả các thay đổi đối với mảng được thực hiện ** trước khi ** tham chiếu của nó được xuất bản, các bảo đảm dễ bay hơi mở rộng đến nội dung của mảng. – assylias

16

Tham chiếu mảng là hoạt động nguyên tử. Vì vậy, độc giả sẽ hoặc nhìn thấy mảng cũ hoặc mảng mới - một trong hai cách trạng thái nhất quán. (set(int,E) tính toán nội dung mảng mới trước khi đặt tham chiếu, do đó mảng phù hợp khi sắp xếp hàng hóa.)

Tham chiếu mảng được đánh dấu là volatile để người đọc không cần sử dụng khóa để xem thay đổi mảng được tham chiếu. (EDIT: Ngoài ra, volatile đảm bảo rằng nhiệm vụ không được sắp xếp lại, điều này sẽ dẫn đến việc chuyển nhượng được thực hiện khi mảng có thể ở trạng thái không nhất quán.)

Khóa ghi là cần thiết để ngăn ngừa sửa đổi đồng thời, có thể dẫn đến mảng giữ dữ liệu không nhất quán hoặc thay đổi bị mất.

+1

Điều này không chính xác 100%. Nguyên nhân của việc thiết lập tham chiếu là không đủ để đảm bảo tính nhất quán, và các quy tắc của Mô hình bộ nhớ Java giải quyết vấn đề này. Ra lệnh viết và sắp xếp lại các lệnh có thể xảy ra, và sau đó một luồng có thể nhận được một tham chiếu trỏ đến một đối tượng không phù hợp. Điều này cũng xảy ra với mẫu kiểm tra kép (xem http://www.ibm.com/developerworks/java/library/j-dcl.html) –

+3

Nó không giống nhau. đọc/ghi vào một biến động là một điều mà JMM gọi là 'hành động đồng bộ' và xác định một rào cản đối với những gì có thể được sắp xếp lại. Xem http://java.sun.com/docs/books/jls/third_edition/html/memory.html – mdma

+1

@Eyal Schneider: Chào mừng bạn đến năm 2004 (xem http://www.ibm.com/developerworks/library/j- jtp03304 /). Đọc phần có tiêu đề "Đảm bảo mới cho dễ bay hơi" –

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