[java] HashMap은 다른 키에 대해 스레드로부터 안전합니까?
HashMap에 액세스하는 두 개의 다중 스레드가 있지만 동시에 동일한 키에 액세스하지 않는다고 보장하면 여전히 경쟁 조건으로 이어질 수 있습니까?
답변
@dotsid의 답변에서 그는 다음과 같이 말합니다.
어떤 식 으로든 HashMap을 변경하면 코드가 손상됩니다.
그는 맞다. 동기화없이 업데이트 된 HashMap 은 스레드가 분리 된 키 세트를 사용 하더라도 중단 됩니다. 다음은 잘못 될 수있는 몇 가지 사항입니다.
-
한 스레드가을 수행하면
put
다른 스레드가 해시 맵 크기에 대해 오래된 값을 볼 수 있습니다. -
스레드가
put
테이블 다시 작성을 트리거하는 작업을 수행하면 다른 스레드가 해시 테이블 배열 참조, 크기, 내용 또는 해시 체인의 일시적이거나 오래된 버전을 볼 수 있습니다. 혼돈이 뒤따를 수 있습니다. -
스레드가
put
다른 스레드에서 사용하는 키와 충돌하는 키에 대해 a를 수행하고 후자 스레드가put
해당 키에 대해 a 를 수행하면 후자는 해시 체인 참조의 오래된 복사본을 볼 수 있습니다. 혼돈이 뒤따를 수 있습니다. -
한 스레드가 다른 스레드의 키 중 하나와 충돌하는 키로 테이블을 검색 할 때 체인에서 해당 키를 만날 수 있습니다. 해당 키에 대해 equals를 호출하고 스레드가 동기화되지 않은 경우 equals 메소드가 해당 키에서 오래된 상태를 만날 수 있습니다.
두 개의 스레드가 동시에 수행 put
하거나 remove
요청 하는 경우 경쟁 조건에 대한 많은 기회가 있습니다.
세 가지 해결책을 생각할 수 있습니다.
- 를 사용합니다
ConcurrentHashMap
. - 규칙적인 것을 사용
HashMap
하지만 외부에서 동기화 하십시오 . 예 : 원시 뮤텍스,Lock
객체 등을 사용합니다. HashMap
스레드마다 다른 것을 사용하십시오 . 스레드에 실제로 분리 된 키 집합이있는 경우 알고리즘 관점에서 단일 맵을 공유 할 필요가 없습니다. 실제로 알고리즘이 어느 시점에서 맵의 키, 값 또는 항목을 반복하는 스레드를 포함하는 경우 단일 맵을 여러 맵으로 분할하면 해당 부분의 처리 속도가 크게 향상 될 수 있습니다.
답변
ConcurrentHashMap을 사용하십시오. ConcurrentHashMap은 잠금이 경쟁 할 가능성을 줄이기 위해 해시 버킷 범위를 포함하는 다중 잠금을 사용합니다. 경합되지 않은 잠금을 획득하면 성능에 미미한 영향이 있습니다.
원래 질문에 답하려면 : javadoc에 따르면지도의 구조가 변경되지 않는 한 괜찮습니다. 이는 요소를 전혀 제거하지 않고 맵에 아직없는 새 키를 추가하지 않음을 의미합니다. 기존 키와 관련된 값을 바꾸는 것이 좋습니다.
여러 스레드가 동시에 해시 맵에 액세스하고 스레드 중 하나 이상이 맵을 구조적으로 수정하는 경우 외부 적으로 동기화되어야합니다. (구조적 수정은 하나 이상의 매핑을 추가하거나 삭제하는 작업입니다. 단순히 인스턴스에 이미 포함 된 키와 관련된 값을 변경하는 것은 구조적 수정이 아닙니다.)
가시성을 보장하지는 않지만. 따라서 때때로 오래된 연관 검색을 기꺼이 받아 들여야합니다.
답변
“액세스”에서 의미하는 바에 따라 다릅니다. 방금 읽는 경우 ” 이전 발생 “규칙에 따라 데이터의 가시성이 보장되는 한 동일한 키도 읽을 수 있습니다 . 즉, HashMap
어떤 독자도 .NET에 액세스하기 전에 변경해서는 안되며 모든 변경 (초기 구성)을 완료해야합니다 HashMap
.
HashMap
어떤 식 으로든 a를 변경하면 코드가 손상됩니다. @Stephen C는 그 이유를 아주 잘 설명합니다.
편집 : 첫 번째 경우가 실제 상황이라면 Collections.unmodifiableMap()
HashMap이 변경되지 않았는지 확인하는 데 사용 하는 것이 좋습니다 . 으로 가리키는 개체 HashMap
도 변경되어서 는 안되므로 적극적인 final
키워드 사용 이 도움이 될 수 있습니다.
그리고 @Lars Andren이 말했듯 ConcurrentHashMap
이 대부분의 경우 최선의 선택입니다.
답변
두 스레드에서 적절한 동기화없이 HashMap을 수정하면 쉽게 경쟁 조건이 발생할 수 있습니다.
put()
내부 테이블의 크기가 조정 되면 시간이 좀 걸리고 다른 스레드는 이전 테이블에 계속 기록합니다.put()
키의 해시 코드가 테이블 크기의 모듈로와 같으면 서로 다른 키에 대해 두 개가 동일한 버킷을 업데이트합니다. (사실 해시 코드와 버킷 인덱스의 관계는 더 복잡하지만 여전히 충돌이 발생할 수 있습니다.)