Nếu tôi có hai luồng truy cập vào một HashMap, nhưng đảm bảo rằng chúng sẽ không bao giờ truy cập cùng một khóa cùng một lúc, điều đó có thể dẫn đến tình trạng chạy đua không?
Nếu tôi có hai luồng truy cập vào một HashMap, nhưng đảm bảo rằng chúng sẽ không bao giờ truy cập cùng một khóa cùng một lúc, điều đó có thể dẫn đến tình trạng chạy đua không?
Câu trả lời:
Trong câu trả lời của @ dotid, anh ấy nói thế này:
Nếu bạn thay đổi HashMap theo bất kỳ cách nào thì mã của bạn chỉ đơn giản là bị hỏng.
Anh ấy đúng. Bản đồ HashMap được cập nhật mà không đồng bộ hóa sẽ bị hỏng ngay cả khi các luồng đang sử dụng các bộ khóa rời rạc. Dưới đây là một số điều có thể xảy ra sai sót.
Nếu một luồng thực hiện a put
, thì một luồng khác có thể thấy giá trị cũ cho kích thước của hashmap.
Khi một luồng thực hiện hành động put
kích hoạt xây dựng lại bảng, một luồng 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 bảng băm, kích thước, nội dung của nó hoặc chuỗi băm. Sự hỗn loạn có thể xảy ra sau đó.
Khi một luồng thực hiện một 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ố luồng khác và luồng sau thực hiện một put
cho khóa của nó, thì luồng sau có thể thấy một bản sao cũ của tham chiếu chuỗi băm. Sự hỗn loạn có thể xảy ra sau đó.
Khi một luồng thăm dò bảng với một khóa va chạm với một trong các khóa của một chuỗi khác, nó có thể gặp khóa đó trên chuỗi. Nó sẽ gọi bằng trên khóa đó và nếu các luồng không được đồng bộ hóa, phương thức bằng có thể gặp phải 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ó rất nhiều cơ hội cho các điều kiện đua.
Tôi có thể nghĩ ra ba giải pháp:
ConcurrentHashMap
.HashMap
nhưng đồng bộ hóa ở bên ngoài; ví dụ: sử dụng mutexes, Lock
đối tượng, etcetera nguyên thủy .HashMap
cho mỗi chủ đề. Nếu các luồng thực sự có một tập hợp khóa rời rạc, thì không cần (từ góc độ thuật toán) để chúng chia sẻ một Bản đồ duy nhất. Thật vậy, nếu các thuật toán của bạn liên quan đến các chuỗi lặp lại các khóa, giá trị hoặc mục nhập của bản đồ tại một số điểm, việc chia nhỏ bản đồ thành nhiều bản đồ có thể giúp tăng tốc đáng kể cho phần đó của quá trình xử lý.Chỉ cần sử dụng ConcurrentHashMap. ConcurrentHashMap sử dụng nhiều khóa bao gồm một loạt các nhóm băm để giảm khả năng khóa bị tranh chấp. Có một tác động hiệu suất biên để có được một khóa không được kiểm tra.
Để 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 xóa các phần tử và không thêm các khóa mới chưa có trong bản đồ. Thay thế giá trị được liên kết với các khóa hiện có là được.
Nếu nhiều luồng truy cập đồng thời vào 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, thì nó phải được đồ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ị được liên kết với khóa mà một thể hiện đã 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 lấy lại các liên kết cũ.
Nó phụ thuộc vào những gì bạn có nghĩa là trong "truy cập". Nếu bạn chỉ đọc, bạn có thể đọc ngay cả các khóa tương tự miễn là khả năng hiển thị của dữ liệu được đảm bảo theo quy tắc " xảy ra trước ". Điều này có nghĩa là HashMap
không nên thay đổi và tất cả các thay đổi (cấu trúc 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 a HashMap
theo bất kỳ cách nào thì mã của bạn chỉ bị hỏng. @Stephen C cung cấp lời 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()
để chắc chắn rằng HashMap của bạn không bao giờ thay đổi. Các đối tượng được trỏ đến HashMap
cũng không nên thay đổi, vì vậy việc sử dụng final
từ khóa tích cực 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.
unmodifiableMap
đảm bảo khách hàng không thể thay đổi bản đồ. Nó không làm gì để đảm bảo rằng bản đồ bên dưới không bị thay đổi.
Việc 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 tình trạng chạy đua.
put()
dẫn đến thay đổi kích thước của bảng nội bộ, điều này mất một thời gian và luồng khác tiếp tục ghi vào bảng cũ.put()
đối với các khóa khác nhau dẫn đến việc cập nhật cùng một nhóm nếu mã băm của các khóa có mô-đun bằng nhau đối với kích thước bảng. (Trên thực tế, mối quan hệ giữa mã băm và chỉ mục nhóm phức tạp hơn, nhưng vẫn có thể xảy ra xung đột.)HashMap
triển khai mà bạn đang sử dụng, bạn có thể bị hỏng HashMap
cấu trúc dữ liệu, vân vân do dị thường bộ nhớ.