Có an toàn để nhận các giá trị từ java.util.HashMap từ nhiều luồng (không sửa đổi) không?


138

Có một trường hợp bản đồ sẽ được xây dựng và một khi nó được khởi tạo, nó sẽ không bao giờ được sửa đổi nữa. Tuy nhiên, nó sẽ được truy cập (chỉ thông qua get (key)) từ nhiều luồng. Có an toàn khi sử dụng java.util.HashMaptheo cách này?

(Hiện nay, tôi đang hạnh phúc bằng cách sử dụng java.util.concurrent.ConcurrentHashMap, và không cần phải đo để cải thiện hiệu suất, nhưng tôi chỉ đơn giản là tò mò nếu một đơn giản HashMapsẽ đủ. Do đó, câu hỏi này là không "Cái nào tôi nên sử dụng?" Cũng không phải là một câu hỏi hiệu suất. Thay vào đó, câu hỏi là "Liệu nó có an toàn không?")


4
Nhiều câu trả lời ở đây là chính xác liên quan đến việc loại trừ lẫn nhau khỏi các luồng đang chạy, nhưng không chính xác về cập nhật bộ nhớ. Tôi đã bình chọn lên / xuống tương ứng, nhưng vẫn còn nhiều câu trả lời không chính xác với phiếu bầu tích cực.
Heath Biên giới

@Heath Biên giới, nếu trường hợp a được khởi tạo tĩnh không thể thay đổi HashMap, thì nó sẽ an toàn cho việc đọc đồng thời (vì các luồng khác không thể bỏ lỡ các bản cập nhật vì không có bản cập nhật), phải không?
kaqqao

Nếu nó được khởi tạo tĩnh và không bao giờ được sửa đổi bên ngoài khối tĩnh, thì có thể ổn vì tất cả khởi tạo tĩnh được đồng bộ hóa bởi ClassLoader. Đó là giá trị một câu hỏi riêng của mình. Tôi vẫn rõ ràng đồng bộ hóa nó và hồ sơ để xác minh rằng nó đang gây ra vấn đề hiệu suất thực sự.
Heath

@HeathBnings - "cập nhật bộ nhớ" nghĩa là gì? JVM là một mô hình chính thức xác định những thứ như khả năng hiển thị, nguyên tử, xảy ra trước các mối quan hệ, nhưng không sử dụng các thuật ngữ như "cập nhật bộ nhớ". Bạn nên làm rõ, tốt nhất là sử dụng thuật ngữ từ JLS.
BeeOnRope

2
@Dave - Tôi cho rằng bạn vẫn không tìm kiếm câu trả lời sau 8 năm, nhưng đối với hồ sơ, sự nhầm lẫn chính trong gần như tất cả các câu trả lời là họ tập trung vào các hành động bạn thực hiện trên đối tượng bản đồ . Bạn đã giải thích rằng bạn không bao giờ sửa đổi đối tượng, vì vậy tất cả đều không liên quan. "Gotcha" tiềm năng duy nhất sau đó là cách bạn xuất bản tài liệu tham khảo đến cái Mapmà bạn không giải thích. Nếu bạn không làm điều đó một cách an toàn, nó không an toàn. Nếu bạn làm điều đó một cách an toàn, nó là . Chi tiết trong câu trả lời của tôi.
BeeOnRope

Câu trả lời:


55

Thành ngữ của bạn là an toàn nếu và chỉ khi tham chiếu đến HashMapđược công bố an toàn . Thay vì bất cứ điều gì liên quan đến nội bộ của HashMapchính nó, ấn phẩm an toàn liên quan đến cách chủ đề xây dựng làm cho tham chiếu đến bản đồ hiển thị cho các chủ đề khác.

Về cơ bản, cuộc đua duy nhất có thể ở đây là giữa việc xây dựng HashMapvà bất kỳ chủ đề đọc nào có thể truy cập nó trước khi nó được xây dựng đầy đủ. Hầu hết các cuộc thảo luận là về những gì xảy ra với trạng thái của đối tượng bản đồ, nhưng điều này không liên quan vì bạn không bao giờ sửa đổi nó - vì vậy phần thú vị duy nhất là cách HashMaptham chiếu được xuất bản.

Ví dụ: hãy tưởng tượng bạn xuất bản bản đồ như thế này:

class SomeClass {
   public static HashMap<Object, Object> MAP;

   public synchronized static setMap(HashMap<Object, Object> m) {
     MAP = m;
   }
}

... và tại một số điểm setMap()được gọi bằng bản đồ và các luồng khác đang sử dụng SomeClass.MAPđể truy cập bản đồ và kiểm tra null như thế này:

HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
  .. use the map
} else {
  .. some default behavior
}

Điều này không an toàn mặc dù nó có thể xuất hiện như thể nó là. Vấn đề là không có mối quan hệ xảy ra trước khi tập hợp SomeObject.MAPvà lần đọc tiếp theo trên một luồng khác, vì vậy luồng đọc được tự do xem bản đồ được xây dựng một phần. Điều này có thể làm bất cứ điều gì và thậm chí trong thực tế, nó thực hiện những việc như đưa luồng đọc vào một vòng lặp vô hạn .

Để xuất bản bản đồ một cách an toàn, bạn cần thiết lập mối quan hệ xảy ra trước khi viết tham chiếu đến HashMap(tức là ấn phẩm ) và các độc giả tiếp theo của tài liệu tham khảo đó (nghĩa là mức tiêu thụ). Thuận tiện, chỉ có một vài cách dễ nhớ để thực hiện điều đó [1] :

  1. Trao đổi tham chiếu thông qua trường được khóa đúng cách ( JLS 17.4.5 )
  2. Sử dụng trình khởi tạo tĩnh để thực hiện các cửa hàng khởi tạo ( JLS 12.4 )
  3. Trao đổi tham chiếu qua trường biến động ( JLS 17.4.5 ) hoặc là hệ quả của quy tắc này, thông qua các lớp AtomicX
  4. Khởi tạo giá trị vào trường cuối cùng ( JLS 17.5 ).

Những cái thú vị nhất cho kịch bản của bạn là (2), (3) và (4). Cụ thể, (3) áp dụng trực tiếp cho mã tôi có ở trên: nếu bạn chuyển đổi khai báo MAPthành:

public static volatile HashMap<Object, Object> MAP;

sau đó tất cả mọi thứ đều hoàn hảo: độc giả thấy một không null giá trị nhất thiết phải có một xảy ra-trước khi mối quan hệ với các cửa hàng để MAPvà do đó thấy tất cả các cửa hàng liên quan đến việc khởi tạo bản đồ.

Các phương thức khác thay đổi ngữ nghĩa của phương thức của bạn, vì cả (2) (sử dụng bộ khởi động tĩnh) và (4) (sử dụng cuối cùng ) ngụ ý rằng bạn không thể thiết lập MAPđộng khi chạy. Nếu bạn không cần phải làm điều đó, thì chỉ cần khai báo MAPstatic final HashMap<>và bạn được đảm bảo xuất bản an toàn.

Trong thực tế, các quy tắc rất đơn giản để truy cập an toàn vào "các đối tượng không bao giờ sửa đổi":

Nếu bạn đang xuất bản một đối tượng vốn không phải là bất biến (như trong tất cả các trường được khai báo final) và:

  • Bạn đã có thể tạo đối tượng sẽ được chỉ định tại thời điểm khai báo a : chỉ cần sử dụng một finaltrường (bao gồm cả static finalcác thành viên tĩnh).
  • Bạn muốn gán đối tượng sau, sau khi tham chiếu đã hiển thị: sử dụng trường biến động b .

Đó là nó!

Trong thực tế, nó rất hiệu quả. static finalVí dụ, việc sử dụng một trường cho phép JVM giả sử giá trị không thay đổi trong vòng đời của chương trình và tối ưu hóa nó rất nhiều. Việc sử dụng finaltrường thành viên cho phép hầu hết các kiến trúc đọc trường theo cách tương đương với trường đọc bình thường và không ức chế tối ưu hóa thêm c .

Cuối cùng, việc sử dụng volatilecó một số tác động: không cần rào cản phần cứng đối với nhiều kiến ​​trúc (như x86, cụ thể là không cho phép đọc để vượt qua đọc), nhưng một số tối ưu hóa và sắp xếp lại có thể không xảy ra trong thời gian biên dịch - nhưng điều này hiệu quả nói chung là nhỏ. Đổi lại, bạn thực sự nhận được nhiều hơn những gì bạn yêu cầu - không chỉ bạn có thể xuất bản một cách an toàn HashMap, bạn có thể lưu trữ nhiều HashMaps chưa được sửa đổi như bạn muốn cùng một tham chiếu và yên tâm rằng tất cả người đọc sẽ thấy một bản đồ được xuất bản an toàn .

Để biết thêm chi tiết, hãy tham khảo Shipilev hoặc Câu hỏi thường gặp này của Manson và Goetz .


[1] Trích dẫn trực tiếp từ shipilev .


a Nghe có vẻ phức tạp, nhưng ý tôi là bạn có thể gán tham chiếu tại thời điểm xây dựng - tại điểm khai báo hoặc trong hàm tạo (trường thành viên) hoặc bộ khởi tạo tĩnh (trường tĩnh).

b Tùy chọn, bạn có thể sử dụng một synchronizedphương thức để lấy / đặt, hoặc một AtomicReferencecái gì đó, nhưng chúng tôi đang nói về công việc tối thiểu bạn có thể làm.

c Một số kiến ​​trúc với các mô hình bộ nhớ rất yếu (tôi đang nhìn vào bạn , Alpha) có thể yêu cầu một số loại rào cản đọc trước khi finalđọc - nhưng ngày nay chúng rất hiếm.


never modify HashMapstate of the map objectTôi nghĩ không phải là chủ đề an toàn. Chúa biết việc thực hiện thư viện, nếu tài liệu chính thức không nói đó là luồng an toàn.
Giang YD

@JiangYD - bạn đúng là có một vùng màu xám ở đó trong một số trường hợp: khi chúng ta nói "sửa đổi" điều chúng ta thực sự muốn nói là bất kỳ hành động nào thực hiện trong nội bộ một số bài viết có thể chạy đua với đọc hoặc viết trên các luồng khác. Các ghi này có thể là chi tiết triển khai nội bộ, do đó, ngay cả một thao tác có vẻ như "chỉ đọc" như get()trên thực tế có thể thực hiện một số ghi, nói rằng cập nhật một số thống kê (hoặc trong trường hợp cập LinkedHashMapnhật theo thứ tự truy cập). Vì vậy, một lớp được viết tốt sẽ cung cấp một số tài liệu cho thấy rõ nếu ...
BeeOnRope

... rõ ràng các hoạt động "chỉ đọc" thực sự chỉ đọc trong nội bộ theo nghĩa an toàn của luồng. Ví dụ, trong thư viện chuẩn C ++, có một quy tắc mền rằng chức năng thành viên được đánh dấu constlà chỉ đọc thực sự theo nghĩa như vậy (bên trong, họ vẫn có thể thực hiện ghi, nhưng chúng sẽ phải được tạo luồng an toàn). Không có consttừ khóa trong Java và tôi không biết về bất kỳ bảo đảm chăn nào được ghi lại, nhưng nói chung, các lớp thư viện tiêu chuẩn hoạt động như mong đợi và các trường hợp ngoại lệ được ghi lại (xem LinkedHashMapví dụ trong đó RO ops getđược đề cập rõ ràng là không an toàn).
BeeOnRope

@JiangYD - cuối cùng, trở lại câu hỏi ban đầu của bạn, vì HashMapchúng tôi thực sự có quyền trong tài liệu về hành vi an toàn luồng cho lớp này: Nếu nhiều luồng truy cập đồng thời một bản đồ băm và ít nhất một trong các luồng sửa đổi cấu trúc bản đồ, nó phải được đồng bộ hóa bên ngoài. (Sửa đổi cấu trúc là bất kỳ thao tác nào thêm hoặc xóa một hoặc nhiều ánh xạ; chỉ thay đổi giá trị được liên kết với khóa mà một thể hiện đã chứa không phải là sửa đổi cấu trúc.)
BeeOnRope

Vì vậy, đối với HashMapcác phương thức mà chúng tôi mong đợi là chỉ đọc, chỉ đọc, vì chúng không sửa đổi cấu trúc HashMap. Tất nhiên, bảo đảm này có thể không áp dụng cho các Maptriển khai khác tùy ý , nhưng câu hỏi là về HashMapcụ thể.
BeeOnRope

70

Jeremy Manson, vị thần khi nói đến Mô hình bộ nhớ Java, có một blog gồm ba phần về chủ đề này - bởi vì thực chất bạn đang đặt câu hỏi "Có an toàn khi truy cập HashMap bất biến" - câu trả lời cho điều đó là có. Nhưng bạn phải trả lời vị ngữ cho câu hỏi đó là - "HashMap của tôi có bất biến không". Câu trả lời có thể làm bạn ngạc nhiên - Java có một bộ quy tắc tương đối phức tạp để xác định tính bất biến.

Để biết thêm thông tin về chủ đề này, hãy đọc các bài đăng trên blog của Jeremy:

Phần 1 về Tính không thay đổi trong Java: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Phần 2 về Tính không thay đổi trong Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Phần 3 về Tính không thay đổi trong Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html


3
Đó là một điểm tốt, nhưng tôi đang dựa vào khởi tạo tĩnh, trong đó không có tài liệu tham khảo nào thoát ra, vì vậy nó sẽ an toàn.
Dave L.

5
Tôi không thấy làm thế nào đây là một câu trả lời được đánh giá cao (hoặc thậm chí là một câu trả lời). Nó không, đối với một, thậm chí trả lời câu hỏi và nó không đề cập đến một nguyên tắc chính sẽ quyết định liệu nó có an toàn hay không: xuất bản an toàn . "Câu trả lời" rút gọn thành "thật khó hiểu" và đây là ba liên kết (phức tạp) bạn có thể đọc.
BeeOnRope

Anh ấy trả lời câu hỏi ở cuối câu đầu tiên. Về mặt câu trả lời, anh ta nêu quan điểm rằng tính bất biến (được nhắc đến trong đoạn đầu của câu hỏi) không đơn giản, cùng với các tài nguyên có giá trị giải thích thêm về chủ đề đó. Các điểm không đo lường xem đó có phải là câu trả lời hay không, nó đo xem câu trả lời có "hữu ích" cho người khác hay không. Câu trả lời được chấp nhận có nghĩa đó là câu trả lời mà OP đang tìm kiếm, câu trả lời của bạn đã nhận được.
Jesse

@Jlie anh ấy không trả lời câu hỏi ở cuối câu đầu tiên, anh ấy trả lời câu hỏi "có an toàn khi truy cập một đối tượng bất biến", điều này có thể hoặc không thể áp dụng cho câu hỏi của OP khi anh ấy chỉ ra trong câu tiếp theo. Về cơ bản, đây gần như là một câu trả lời kiểu "tự mình tìm ra" liên kết, đây không phải là một câu trả lời hay cho SO. Đối với các upvote, tôi nghĩ đó là một chức năng của 10,5 tuổi và là một chủ đề thường được tìm kiếm. Nó chỉ nhận được rất ít lượt upvote trong vài năm qua nên có thể mọi người sẽ quay lại :).
BeeOnRope

35

Việc đọc được an toàn từ quan điểm đồng bộ hóa nhưng không phải là quan điểm bộ nhớ. Đây là điều bị hiểu lầm rộng rãi giữa các nhà phát triển Java, kể cả ở đây trên Stackoverflow. (Quan sát đánh giá của câu trả lời này để chứng minh.)

Nếu bạn có các luồng khác đang chạy, chúng có thể không thấy bản sao cập nhật của HashMap nếu không có bộ nhớ ghi ra khỏi luồng hiện tại. Ghi bộ nhớ xảy ra thông qua việc sử dụng các từ khóa được đồng bộ hóa hoặc biến động hoặc thông qua việc sử dụng một số cấu trúc đồng thời java.

Xem bài viết của Brian Goetz về Mô hình bộ nhớ Java mới để biết chi tiết.


Xin lỗi vì Heath đệ trình kép, tôi chỉ nhận thấy bạn sau khi tôi gửi. :)
Alexander

2
Tôi chỉ vui mừng vì có những người khác ở đây thực sự hiểu được các hiệu ứng bộ nhớ.
Heath Biên giới

1
Thật vậy, mặc dù không có luồng nào sẽ nhìn thấy đối tượng trước khi nó được khởi tạo đúng cách, vì vậy tôi không nghĩ đó là một mối quan tâm trong trường hợp này.
Dave L.

1
Điều đó phụ thuộc hoàn toàn vào cách đối tượng được khởi tạo.
Bill Michell

1
Câu hỏi nói rằng một khi HashMap đã được khởi tạo, anh ta không có ý định cập nhật thêm nữa. Từ đó trở đi, anh ta chỉ muốn sử dụng nó như một cấu trúc dữ liệu chỉ đọc. Tôi nghĩ, sẽ an toàn khi làm như vậy, miễn là dữ liệu được lưu trữ trong Bản đồ của anh ấy là bất biến.
Binita Bharati

9

Sau khi tìm hiểu thêm một chút, tôi đã tìm thấy điều này trong tài liệu java (nhấn mạnh của tôi):

Lưu ý rằng việc thực hiện này không được đồng bộ hóa. Nếu nhiều luồng truy cập đồng thời một bản đồ băm và ít nhất một trong các luồng sửa đổi bản đồ theo cấu trúc, nó phải được đồng bộ hóa bên ngoài. (Sửa đổi cấu trúc là bất kỳ thao tác nào thêm hoặc xóa một hoặc nhiều ánh xạ; chỉ thay đổi giá trị được liên kết với khóa mà một thể hiện đã chứa không phải là sửa đổi cấu trúc.)

Điều này dường như ngụ ý rằng nó sẽ an toàn, giả sử điều ngược lại của tuyên bố là đúng.


1
Trong khi đây là lời khuyên tuyệt vời, như các câu trả lời khác nêu, có một câu trả lời nhiều sắc thái hơn trong trường hợp bản đồ được công bố an toàn, bất biến. Nhưng bạn chỉ nên làm điều đó nếu bạn biết bạn đang làm gì.
Alex Miller

1
Hy vọng với những câu hỏi như thế này nhiều hơn chúng ta có thể biết những gì chúng ta đang làm.
Dave L.

Điều này không thực sự chính xác. Như các câu trả lời khác nêu, phải có một sự cố xảy ra trước khi sửa đổi lần cuối và tất cả các lần đọc "an toàn luồng" tiếp theo. Thông thường, điều này có nghĩa là bạn phải xuất bản một cách an toàn đối tượng sau khi nó được tạo và các sửa đổi của nó được thực hiện. Xem câu đầu tiên, đánh dấu câu trả lời đúng.
markspace

9

Một lưu ý là trong một số trường hợp, get () từ HashMap không đồng bộ có thể gây ra một vòng lặp vô hạn. Điều này có thể xảy ra nếu put () đồng thời gây ra sự thử lại của Bản đồ.

http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html


1
Trên thực tế tôi đã thấy điều này treo JVM mà không tiêu tốn CPU (có lẽ tệ hơn)
Peter Lawrey

2
Tôi nghĩ rằng mã này đã được viết lại sao cho không còn có thể có được vòng lặp vô hạn. Nhưng bạn vẫn không nên nhấn và nhận từ HashMap không đồng bộ vì những lý do khác.
Alex Miller

@AlexMiller thậm chí ngoài các lý do khác (tôi cho rằng bạn đang đề cập đến xuất bản an toàn), tôi không nghĩ rằng một thay đổi triển khai nên là một lý do để nới lỏng các hạn chế truy cập, trừ khi được tài liệu cho phép rõ ràng. Khi điều đó xảy ra, HashMap Javadoc cho Java 8 vẫn chứa cảnh báo này:Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.
shmosel

8

Có một thay đổi quan trọng mặc dù. Truy cập bản đồ là an toàn, nhưng nói chung, không đảm bảo rằng tất cả các luồng sẽ nhìn thấy chính xác cùng một trạng thái (và do đó là các giá trị) của HashMap. Điều này có thể xảy ra trên các hệ thống đa bộ xử lý trong đó các sửa đổi đối với HashMap được thực hiện bởi một luồng (ví dụ: luồng được tạo ra) có thể nằm trong bộ đệm của CPU đó và sẽ không được nhìn thấy bởi các luồng chạy trên các CPU khác, cho đến khi hoạt động của hàng rào bộ nhớ thực hiện đảm bảo sự kết hợp bộ nhớ cache. Đặc tả ngôn ngữ Java được trình bày rõ ràng về vấn đề này: giải pháp là có được một khóa (được đồng bộ hóa (...)) để phát ra một hoạt động hàng rào bộ nhớ. Vì vậy, nếu bạn chắc chắn rằng sau khi điền HashMap, mỗi luồng sẽ thu được BẤT K lock khóa nào, thì từ thời điểm đó trở đi để truy cập HashMap từ bất kỳ luồng nào cho đến khi HashMap được sửa đổi lại.


Tôi không chắc rằng luồng truy cập nó sẽ có được bất kỳ khóa nào, nhưng tôi chắc chắn họ sẽ không tham chiếu đến đối tượng cho đến khi nó được khởi tạo, vì vậy tôi không nghĩ rằng họ có thể có một bản sao cũ.
Dave L.

@Alex: Tham chiếu đến HashMap có thể không ổn định để tạo ra các đảm bảo khả năng hiển thị bộ nhớ tương tự. @ Dave: Nó tốt để xem tài liệu tham khảo để objs mới trước khi công việc của ctor của nó trở nên rõ ràng vào chủ đề của bạn.
Chris Vest

@Christian Trong trường hợp chung, chắc chắn. Tôi đã nói rằng trong mã này, nó không phải là.
Dave L.

Nhận được khóa RANDOM không đảm bảo xóa toàn bộ bộ đệm cpu của luồng. Nó phụ thuộc vào việc triển khai JVM và rất có thể nó không được thực hiện theo cách này.
Pierre

Tôi đồng ý với Pierre, tôi không nghĩ rằng có được bất kỳ khóa nào là đủ. Bạn phải đồng bộ hóa trên cùng một khóa để các thay đổi được hiển thị.
damluar

5

Theo http://www.ibm.com/developerworks/java/l Library / j-jtp03304 / # Khởi tạo an toàn, bạn có thể biến HashMap của mình thành trường cuối cùng và sau khi nhà xây dựng kết thúc, nó sẽ được xuất bản một cách an toàn.

... Trong mô hình bộ nhớ mới, có một cái gì đó tương tự như mối quan hệ xảy ra trước khi ghi một trường cuối cùng trong một hàm tạo và tải ban đầu của một tham chiếu được chia sẻ đến đối tượng đó trong một luồng khác. ...


Câu trả lời này có chất lượng thấp, nó giống như câu trả lời từ @taylor gauthier nhưng với ít chi tiết hơn.
Snicolas

1
Ummmm ... không phải là một ass, nhưng bạn có nó ngược. Taylor nói "không, hãy xem bài đăng trên blog này, câu trả lời có thể làm bạn ngạc nhiên", trong khi câu trả lời này thực sự bổ sung một điều mới mà tôi không biết ... Về mối quan hệ xảy ra trước khi viết trường cuối cùng trong một constructor. Câu trả lời này là tuyệt vời, và tôi rất vui khi đọc nó.
Ajax

Huh? Đây là câu trả lời đúng duy nhất tôi tìm thấy sau khi cuộn qua các câu trả lời được đánh giá cao hơn. Chìa khóa được công bố một cách an toàn và đây là câu trả lời duy nhất thậm chí đề cập đến nó.
BeeOnRope

3

Vì vậy, kịch bản bạn mô tả là bạn cần đưa một loạt dữ liệu vào Bản đồ, sau đó khi bạn hoàn thành việc điền dữ liệu, bạn coi nó là bất biến. Một cách tiếp cận "an toàn" (có nghĩa là bạn đang thực thi rằng nó thực sự được coi là bất biến) là thay thế tham chiếu bằng Collections.unmodifiableMap(originalMap)khi bạn sẵn sàng biến nó thành bất biến.

Để biết ví dụ về cách bản đồ xấu có thể thất bại nếu được sử dụng đồng thời và cách giải quyết được đề xuất mà tôi đã đề cập, hãy xem mục diễu hành lỗi này: bug_id = 6423457


2
Điều này là "an toàn" ở chỗ nó thực thi tính bất biến, nhưng nó không giải quyết được vấn đề an toàn luồng. Nếu bản đồ an toàn để truy cập với trình bao bọc UnmodifiableMap thì nó an toàn nếu không có nó và ngược lại.
Dave L.

2

Câu hỏi này được giải quyết trong cuốn sách "Java đồng thời trong thực tiễn" của Brian Goetz (Liệt kê 16.8, trang 350):

@ThreadSafe
public class SafeStates {
    private final Map<String, String> states;

    public SafeStates() {
        states = new HashMap<String, String>();
        states.put("alaska", "AK");
        states.put("alabama", "AL");
        ...
        states.put("wyoming", "WY");
    }

    public String getAbbreviation(String s) {
        return states.get(s);
    }
}

statesđược khai báo finalvà việc khởi tạo của nó được hoàn thành trong hàm tạo của chủ sở hữu, nên bất kỳ luồng nào sau này đọc bản đồ này đều được đảm bảo xem nó tại thời điểm hàm tạo kết thúc, miễn là không có luồng nào khác sẽ cố gắng sửa đổi nội dung của bản đồ.


1

Được cảnh báo rằng ngay cả trong mã đơn luồng, việc thay thế ConcảnHashMap bằng HashMap có thể không an toàn. ConcảnHashMap cấm null là khóa hoặc giá trị. HashMap không cấm họ (đừng hỏi).

Vì vậy, trong trường hợp không chắc là mã hiện tại của bạn có thể thêm null vào bộ sưu tập trong khi thiết lập (có thể là trong trường hợp thất bại nào đó), thay thế bộ sưu tập như mô tả sẽ thay đổi hành vi chức năng.

Điều đó nói rằng, miễn là bạn không làm gì khác đồng thời đọc từ HashMap là an toàn.

[Chỉnh sửa: bằng cách "đọc đồng thời", ý tôi là không có sửa đổi đồng thời.

Các câu trả lời khác giải thích làm thế nào để đảm bảo điều này. Một cách là làm cho bản đồ trở nên bất biến, nhưng nó không cần thiết. Ví dụ, mô hình bộ nhớ JSR133 xác định rõ ràng bắt đầu một luồng là một hành động được đồng bộ hóa, có nghĩa là những thay đổi được thực hiện trong luồng A trước khi nó bắt đầu luồng B được hiển thị trong luồng B.

Mục đích của tôi là không mâu thuẫn với những câu trả lời chi tiết hơn về Mô hình bộ nhớ Java. Câu trả lời này nhằm chỉ ra rằng ngay cả ngoài các vấn đề tương tranh, có ít nhất một sự khác biệt về API giữa ConcảnHashMap và HashMap, có thể làm hỏng ngay cả một chương trình đơn luồng thay thế một chương trình khác.


Cảm ơn cảnh báo, nhưng không có nỗ lực sử dụng khóa hoặc giá trị null.
Dave L.

Nghĩ rằng sẽ không có. null trong các bộ sưu tập là một góc điên rồ của Java.
Steve Jessop

Tôi không đồng ý với câu trả lời này. "Đọc đồng thời từ HashMap là an toàn" bởi chính nó là không chính xác. Nó không nêu rõ các lần đọc đang xảy ra đối với bản đồ có thể thay đổi hoặc không thay đổi. Để chính xác, bạn nên đọc "Đọc đồng thời từ HashMap bất biến là an toàn"
Taylor Gautier

2
Không theo các bài viết mà bạn tự liên kết đến: yêu cầu là bản đồ không được thay đổi (và các thay đổi trước đó phải được hiển thị cho tất cả các luồng người đọc), không phải là bất biến (đó là thuật ngữ kỹ thuật trong Java và là một điều kiện đủ nhưng không cần thiết cho sự an toàn).
Steve Jessop

Ngoài ra một lưu ý ... khởi tạo một lớp hoàn toàn đồng bộ hóa trên cùng một khóa (vâng, bạn có thể bế tắc trong các trình khởi tạo trường tĩnh), vì vậy nếu việc khởi tạo của bạn xảy ra tĩnh, thì không ai có thể thấy nó trước khi quá trình khởi tạo hoàn tất, vì chúng sẽ phải bị chặn trong phương thức ClassLoader.loadClass trên cùng một khóa có được ... Và nếu bạn đang tự hỏi về các trình nạp lớp khác nhau có các bản sao khác nhau của cùng một trường, bạn sẽ đúng ... nhưng điều đó sẽ trực giao với khái niệm về điều kiện chủng tộc; các trường tĩnh của một trình nạp lớp chia sẻ một hàng rào bộ nhớ.
Ajax

0

http://www.docjar.com/html/api/java/util/HashMap.java.html

đây là nguồn cho HashMap. Như bạn có thể nói, hoàn toàn không có mã khóa / mutex ở đó.

Điều này có nghĩa là mặc dù bạn có thể đọc từ HashMap trong tình huống đa luồng, nhưng tôi chắc chắn sẽ sử dụng một concảnHashMap nếu có nhiều lần ghi.

Điều thú vị là cả .NET HashTable và Dictionary <K, V> đều được tích hợp mã đồng bộ hóa.


2
Tôi nghĩ rằng có những lớp chỉ đơn giản là đọc đồng thời có thể khiến bạn gặp rắc rối, vì việc sử dụng nội bộ các biến thể hiện tạm thời chẳng hạn. Vì vậy, một người có lẽ cần phải kiểm tra cẩn thận nguồn, hơn là quét nhanh để khóa / mã mutex.
Dave L.

0

Nếu việc khởi tạo và mọi lệnh được đồng bộ hóa, bạn sẽ lưu lại.

Mã sau được lưu vì trình nạp lớp sẽ đảm nhiệm việc đồng bộ hóa:

public static final HashMap<String, String> map = new HashMap<>();
static {
  map.put("A","A");

}

Mã sau được lưu vì việc viết biến động sẽ đảm nhiệm việc đồng bộ hóa.

class Foo {
  volatile HashMap<String, String> map;
  public void init() {
    final HashMap<String, String> tmp = new HashMap<>();
    tmp.put("A","A");
    // writing to volatile has to be after the modification of the map
    this.map = tmp;
  }
}

Điều này cũng sẽ hoạt động nếu biến thành viên là cuối cùng vì cuối cùng cũng biến động. Và nếu phương thức là một constructor.

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.