[java] 여러 스레드에서 java.util.HashMap의 값을 얻는 것이 안전합니까?

맵이 생성되는 경우가 있으며 일단 초기화되면 다시는 수정되지 않습니다. 그러나 여러 스레드에서 (get (key)만을 통해) 액세스됩니다. java.util.HashMap이런 식으로 사용하는 것이 안전 합니까?

(현재, 내가 행복하게을 사용하고 java.util.concurrent.ConcurrentHashMap, 성능을 개선 할 측정 필요가 없지만, 간단한 경우 단순히 궁금 HashMap충분할 것입니다. 따라서,이 질문은 하지 ? “하나는 내가 사용해야하는”도 아니다 성능 질문입니다. 문제는 “안전할까요?”입니다.



답변

귀하의 관용구는 참조 가 안전하게 게시 된 경우에만 안전 합니다 . 안전한 게시 는 자체 내부와 관련된 것이 아니라 구성 스레드가 다른 스레드에서 맵에 대한 참조를 표시하는 방법을 처리합니다.HashMapHashMap

기본적으로 여기서 가능한 유일한 경주 HashMap는 완전히 구성되기 전에 액세스 할 수있는 읽기 스레드와 읽기 스레드 사이의 구성입니다 . 대부분의 토론은지도 객체의 상태에 대한 문제에 관한 것이지만, 절대 수정하지 않기 때문에 이는 관련이 없습니다. 따라서 흥미로운 부분은 HashMap참조가 게시되는 방법입니다.

예를 들어, 다음과 같이지도를 게시한다고 가정하십시오.

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

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

… 어느 시점 setMap()에서 맵과 함께 호출되고 다른 스레드가 SomeClass.MAP맵에 액세스하는 데 사용 하고 다음과 같이 null을 확인합니다.

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

이다 안전하지 그것이 것처럼 아마 나타나더라도. 문제가없는 것이 없다 일 – 전 세트 사이의 관계 SomeObject.MAP와 다른 스레드에서 이후의 읽기, 읽기 스레드가 부분적으로 구축지도를 볼 무료 있도록. 이것은 거의 모든 작업을 수행 할 수 있으며 실제로 읽기 스레드를 무한 루프에 넣는 것과 같은 작업을 수행합니다 .

안전지도를 게시하려면이 설정해야합니다 발생-전에 사이의 관계 참조 서면으로 받는 HashMap(즉, 출판 ) 및 해당 참조 (즉, 소비)의 후속 독자. 편리하게, 이를 달성 하기 위한 기억하기 쉬운 몇 가지 방법 이 있습니다 [1] :

  1. 올바르게 잠긴 필드를 통해 참조 교환 ( JLS 17.4.5 )
  2. 정적 초기화 프로그램을 사용하여 초기화 저장소를 수행하십시오 ( JLS 12.4 )
  3. 변동성 필드 ( JLS 17.4.5 )를 통해 또는이 규칙의 결과로 AtomicX 클래스를 통해 참조를 교환하십시오.
  4. 최종 필드 ( JLS 17.5 ) 로 값을 초기화하십시오 .

시나리오에서 가장 흥미로운 것은 (2), (3) 및 (4)입니다. 특히 (3)은 위의 코드에 직접 적용됩니다 MAP.

public static volatile HashMap<Object, Object> MAP;

아닌 값 을 보는 독자는 반드시 상점과 관계가 있어야 하기MAP 때문에 맵 초기화와 연관된 모든 상점을 볼 수 있습니다.

(2) (정적 이니셜 라이저 사용) 및 (4) ( final 사용 ) 모두 MAP런타임에 동적으로 설정할 수 없음을 의미하므로 다른 메소드는 메소드의 의미를 변경합니다 . 당신이하지 않으면 필요 그렇게, 그럼 그냥 선언 MAPA와 static final HashMap<>당신은 안전 게시를 보장합니다.

실제로 규칙은 “수정되지 않은 개체”에 안전하게 액세스하기 위해 간단합니다.

본질적으로 불변 이 아닌 객체를 게시하는 경우 (선언 된 모든 필드에서 final와 같이)

  • 선언 순간에 할당 될 객체를 이미 만들 수 있습니다. a : final필드를 사용하십시오 ( static final정적 멤버 포함).
  • 참조가 이미 표시된 후에 나중에 오브젝트를 지정하려고합니다 . 휘발성 필드 b를 사용하십시오 .

그게 다야!

실제로는 매우 효율적입니다. a의 사용 static final분야는, 예를 들어, JVM은 값이 프로그램의 삶에 대한 변경 가정하고 무겁게을 최적화 할 수 있습니다. (A)의 사용 final부재 필드 있도록 대부분의 구조는 일반 필드 판독하는 방법 당량의 필드를 판독하고 억제되지 않는 상기 최적화 된 C를 .

마지막으로 사용하면 volatile영향을 미칩니다. 많은 아키텍처 (예 : x86, 특히 읽기가 읽기를 허용하지 않는 아키텍처)에는 하드웨어 장벽이 필요하지 않지만 컴파일시 일부 최적화 및 재정렬이 발생하지 않을 수 있습니다. 효과는 일반적으로 작습니다. 그 대가로 실제로 요청한 것보다 더 많은 것을 얻을 수 있습니다. 안전하게 게시 할 수있을뿐만 아니라 동일한 참조에 대해 원하는만큼 HashMap수정하지 않은 수를 더 많이 저장할 수 HashMap있으며 모든 독자에게 안전하게 게시 된지도가 표시됩니다. .

자세한 내용은 Shipilev 또는 Manson and Goetz의 FAQ를 참조하십시오 .


[1] shipilev 에서 직접 인용 .


a 복잡해 보이지만, 선언 시점이나 생성자 (멤버 필드) 또는 정적 초기화 기 (정적 필드)에서 생성시 참조를 할당 할 수 있습니다.

b 선택적으로 synchronized메소드를 사용 하여 가져 오거나 설정할 수 AtomicReference있지만 할 수있는 최소 작업에 대해 이야기하고 있습니다.

c 매우 약한 메모리 모델을 가진 일부 아키텍처 ( 알파, 나는 당신을보고 있습니다 )는 읽기 전에 어떤 유형의 읽기 장벽이 필요할 수 final있지만 오늘날에는 매우 드 rare니다.


답변

Java 메모리 모델에 관한 신인 Jeremy Manson은이 주제에 대해 세 부분으로 구성된 블로그를 가지고 있습니다. 본질적으로 “불변 HashMap에 액세스해도 안전합니까?”라는 질문을하기 때문입니다. 그러나 “내 HashMap이 변경 불가능하다”라는 질문에 대한 술어에 응답해야합니다. Java는 불변성을 결정하기 위해 비교적 복잡한 규칙 세트를 가지고 있습니다.

이 주제에 대한 자세한 내용은 Jeremy의 블로그 게시물을 참조하십시오.

Java 불변성에 대한 1 부 :
http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Java 불변성에 대한 2 부 :
http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Java 불변성에 대한 3 부 :
http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html


답변

읽기는 동기화 관점에서는 안전하지만 메모리 관점에서는 안전하지 않습니다. 이것은 여기에 Stackoverflow를 포함하여 Java 개발자들 사이에서 널리 이해되는 것입니다. ( 증거 를 위해이 답변 의 등급을 준수하십시오 .)

다른 스레드가 실행중인 경우 현재 스레드에서 메모리 쓰기가 없으면 업데이트 된 HashMap 사본이 표시되지 않을 수 있습니다. 메모리 쓰기는 동기화 또는 휘발성 키워드를 사용하거나 일부 Java 동시성 구성을 사용하여 발생합니다.

자세한 내용 은 새로운 Java 메모리 모델에 대한 Brian Goetz의 기사 를 참조하십시오.


답변

좀 더 살펴본 후에 java doc (강조 광산) 에서 이것을 발견했습니다 .

이 구현은 동기화되지 않습니다. 여러 스레드가 동시에 해시 맵에 액세스하고 하나 이상의 스레드가 맵을 구조적으로 수정하는 경우 외부 적으로 동기화해야합니다. (구조적 수정은 하나 이상의 매핑을 추가하거나 삭제하는 작업입니다. 인스턴스에 이미 포함 된 키와 관련된 값을 변경하는 것만으로는 구조적 수정이 아닙니다.)

이것은 진술의 반대가 사실이라고 가정하면 안전 할 것임을 암시하는 것처럼 보입니다.


답변

어떤 상황에서는 동기화되지 않은 HashMap의 get ()이 무한 루프를 일으킬 수 있습니다. 동시 put ()으로 인해 맵이 다시 해시되는 경우에 발생할 수 있습니다.

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


답변

그래도 중요한 비틀기가 있습니다. 맵에 액세스하는 것이 안전하지만 일반적으로 모든 스레드가 HashMap과 정확히 동일한 상태 (및 값)를 볼 수있는 것은 아닙니다. 하나의 스레드 (예 : 채워진 스레드)에 의해 수행 된 HashMap에 대한 수정 사항이 해당 CPU 캐시에있을 수 있고 메모리 펜스 작업이 완료 될 때까지 다른 CPU에서 실행중인 스레드에 표시되지 않는 다중 프로세서 시스템에서 발생할 수 있습니다. 캐시 일관성 보장을 수행했습니다. Java 언어 사양은 이것에 대해 명시 적입니다. 해결책은 메모리 차단 작업을 수행하는 잠금 (동기화 (…))을 얻는 것입니다. 따라서 HashMap을 채운 후에 각 스레드가 ANY 잠금을 획득한다고 확신하면 그 시점부터 HashMap이 다시 수정 될 때까지 모든 스레드에서 HashMap에 액세스해도됩니다.


답변

http://www.ibm.com/developerworks/java/library/j-jtp03304/ # 초기화 안전 에 따르면 HashMap을 최종 필드로 만들 수 있으며 생성자가 완료된 후에 안전하게 게시됩니다.

… 새로운 메모리 모델에는 생성자의 최종 필드 쓰기와 다른 스레드의 해당 객체에 대한 공유 참조의 초기로드간에 발생하는 이전 관계와 유사한 것이 있습니다. …


댓글 달기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다