Nếu tôi có hai chủ đề truy cập một HashMap, nhưng đảm bảo rằng họ sẽ không bao giờ truy cập vào cùng một khóa cùng một lúc, điều đó vẫn có thể dẫn đến một điều kiện chủng tộc?Có phải là một chuỗi an toàn HashMap cho các khóa khác nhau không?
Trả lời
Trong câu trả lời @ dotsid của anh nói này:
Nếu bạn thay đổi một HashMap trong bất kỳ cách nào sau đó mã của bạn chỉ đơn giản là bị hỏng.
Anh ấy đúng.Một HashMap được cập nhật mà không đồng bộ hóa sẽ phá vỡ thậm chí nếu các chủ đề đang sử dụng các tập hợp các phím. Đây là một số trong những điều có thể đi sai.
Nếu một chủ đề thực hiện
put
, thì một chuỗi khác có thể thấy giá trị cũ cho kích thước của băm.Khi một chủ đề thực hiện việc xây dựng lại bảng, một chuỗi khác có thể thấy các phiên bản tạm thời hoặc cũ của tham chiếu mảng có thể bắt buộc, kích thước của nó, nội dung của nó hoặc chuỗi băm. Hỗn loạn có thể xảy ra.
Khi chuỗi chỉ
put
cho một khóa va chạm với một số khóa được sử dụng bởi một số chủ đề khác và chuỗi thứ hai thực hiệnput
cho khóa của nó, sau đó có thể thấy một bản sao cũ của tham chiếu chuỗi băm. Hỗn loạn có thể xảy ra.Khi một chủ đề thăm dò bảng có khóa va chạm với một trong các khóa của một số chủ đề khác, nó có thể gặp phải khóa đó trên chuỗi. Nó sẽ gọi bằng với khóa đó, và nếu các luồng không được đồng bộ, phương thức equals có thể gặp trạng thái cũ trong khóa đó.
Và nếu bạn có hai chủ đề đồng thời thực hiện put
hoặc remove
yêu cầu, có nhiều cơ hội cho điều kiện chủng tộc.
tôi có thể nghĩ đến ba giải pháp:
- Sử dụng một
ConcurrentHashMap
. - Sử dụng thông thường
HashMap
nhưng đồng bộ hóa ở bên ngoài; ví dụ. sử dụng mutex nguyên thủy,Lock
đối tượng, v.v. - Sử dụng khác nhau
HashMap
cho mỗi chuỗi. Nếu các chủ đề thực sự có một tập hợp các phím, thì sẽ không cần (từ góc độ thuật toán) để chúng chia sẻ một Bản đồ đơn lẻ. Thật vậy, nếu thuật toán của bạn liên quan đến các chủ đề lặp lại các khóa, giá trị hoặc mục của bản đồ tại một thời điểm nào đó, việc tách bản đồ thành nhiều bản đồ có thể tăng tốc đáng kể cho phần đó của quá trình xử lý.
Tùy thuộc vào ý bạn trong "truy cập". Nếu bạn chỉ đọc, bạn có thể đọc ngay cả các phím tương tự miễn là khả năng hiển thị của dữ liệu được bảo đảm theo "happens-before" quy tắc. Điều này có nghĩa là HashMap
không được thay đổi và tất cả thay đổi (công trình xây dựng ban đầu) phải được hoàn thành trước khi bất kỳ người đọc nào bắt đầu truy cập HashMap
.
Nếu bạn thay đổi HashMap
bằng bất kỳ cách nào thì mã của bạn sẽ bị hỏng. @Stephen C cung cấp giải thích rất tốt tại sao.
CHỈNH SỬA: Nếu trường hợp đầu tiên là tình huống thực tế của bạn, tôi khuyên bạn nên sử dụng Collections.unmodifiableMap()
để đảm bảo rằng HashMap của bạn không bao giờ bị thay đổi. Các đối tượng được chỉ bằng HashMap
cũng không được thay đổi, vì vậy, việc sử dụng từ khóa final
có thể giúp bạn.
Và như @Lars Andren nói, ConcurrentHashMap
là lựa chọn tốt nhất trong hầu hết các trường hợp.
Làm thế nào về ConcurrentHashMap? –
ConcurrentHashMap là một lựa chọn tốt nhất theo ý kiến của tôi. Lý do duy nhất tôi không đề nghị nó, vì tác giả không hỏi nó :) Nó có ít thông lượng hơn vì các hoạt động CAS, nhưng như quy tắc vàng của lập trình đồng thời nói: "Làm cho nó đúng, và chỉ sau đó làm cho nó nhanh ":) –
' unmodifiableMap' đảm bảo khách hàng không thể thay đổi bản đồ. Nó không có gì để đảm bảo rằng bản đồ cơ bản không thay đổi. –
Chỉ cần sử dụng ConcurrentHashMap. ConcurrentHashMap sử dụng nhiều khóa bao gồm một loạt các thùng băm để giảm nguy cơ khóa bị tranh cãi. Có một tác động hiệu suất cận biên để có được một khóa không bị cản trở.
Để trả lời câu hỏi ban đầu của bạn: Theo javadoc, miễn là cấu trúc của bản đồ không thay đổi, bạn vẫn ổn. Điều này có nghĩa là không loại bỏ các yếu tố nào cả và không thêm các khóa mới chưa có trong bản đồ. Thay thế giá trị được kết hợp với các khóa hiện có là tốt.
Nếu nhiều chủ đề truy cập bản đồ băm đồng thời và ít nhất một trong các chủ đề sửa đổi bản đồ cấu trúc, thì phải đồng bộ hóa bên ngoài. (Sửa đổi cấu trúc là bất kỳ hoạt động nào thêm hoặc xóa một hoặc nhiều ánh xạ; chỉ thay đổi giá trị liên quan đến khóa mà một cá thể đã chứa không phải là một sửa đổi cấu trúc.)
Mặc dù nó không đảm bảo về khả năng hiển thị. Vì vậy, bạn phải sẵn sàng chấp nhận thỉnh thoảng các liên kết cũ.
+1 cho độ chính xác kỹ thuật (nếu không thực hành tốt nhất :-) – finnw
Sửa đổi HashMap mà không đồng bộ hóa thích hợp từ hai luồng có thể dễ dàng dẫn đến điều kiện chủng tộc.
- Khi số
put()
dẫn đến thay đổi kích thước bảng nội bộ, việc này mất một chút thời gian và chuỗi khác tiếp tục ghi vào bảng cũ. - Hai
put()
cho các khóa khác nhau dẫn đến bản cập nhật của cùng một nhóm nếu mã băm của khóa bằng modulo kích thước bảng. (Trên thực tế, mối quan hệ giữa hashcode và chỉ số xô là phức tạp hơn, nhưng va chạm vẫn có thể xảy ra.)
4. Sử dụng HashMap thông thường và sử dụng ReentrantReadWriteLock để cho phép nhiều đồng thời độc giả, nhưng độc quyền để viết các hoạt động. –
Subcase của 2. :-) Tôi không nghĩ rằng có bất kỳ cần phải liệt kê những cách khác nhau mà người ta có thể đồng bộ hóa truy cập vào một HashMap thường xuyên. –
Có thể sử dụng ** khối ** được đồng bộ hóa cho điều này không? –