Một chuỗi HashMap có an toàn cho các khóa khác nhau không?


87

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:


99

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 putkí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 putcho 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 putcho 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 puthoặc removeyê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:

  1. Sử dụng a ConcurrentHashMap.
  2. Sử dụng một thường xuyên HashMapnhưng đồng bộ hóa ở bên ngoài; ví dụ: sử dụng mutexes, Lockđối tượng, etcetera nguyên thủy .
  3. Sử dụng khác nhau HashMapcho 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ý.

30

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ũ.


6

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à HashMapkhô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 HashMaptheo 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 HashMapcũng không nên thay đổi, vì vậy việc sử dụng finaltừ khóa tích cực có thể giúp bạn.

Và như @Lars Andren nói, ConcurrentHashMaplà lựa chọn tốt nhất trong hầu hết các trường hợp.


2
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 đề xuất 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: "Hãy làm cho nó đúng và chỉ sau đó làm cho nó nhanh ":)
Denis Bazhenov

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.
Pete Kirkham

Như tôi đã chỉ ra: "Đối tượng được trỏ bởi HashMap không nên thay đổi cũng"
Denis Bazhenov

4

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.

  • Khi một 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ũ.
  • Hai 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.)

1
Nó tồi tệ hơn chỉ là điều kiện chủng tộc. Tùy thuộc vào nội bộ của HashMaptriển khai mà bạn đang sử dụng, bạn có thể bị hỏng HashMapcấu trúc dữ liệu, vân vân do dị thường bộ nhớ.
Stephen C
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.