IT

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

lottoking 2020. 6. 27. 10:39
반응형

여러 스레드에서 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 That sounds complicated, but what I mean is that you can assign the reference at construction time - either at the declaration point or in the constructor (member fields) or static initializer (static fields).

b Optionally, you can use a synchronized method to get/set, or an AtomicReference or something, but we're talking about the minimum work you can do.

c Some architectures with very weak memory models (I'm looking at you, Alpha) may require some type of read barrier before a final read - but these are very rare today.


Jeremy Manson, the god when it comes to the Java Memory Model, has a three part blog on this topic - because in essence you are asking the question "Is it safe to access an immutable HashMap" - the answer to that is yes. But you must answer the predicate to that question which is - "Is my HashMap immutable". The answer might surprise you - Java has a relatively complicated set of rules to determine immutability.

For more info on the topic, read Jeremy's blog posts:

Part 1 on Immutability in Java: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Part 2 on Immutability in Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Part 3 on Immutability in Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html


The reads are safe from a synchronization standpoint but not a memory standpoint. This is something that is widely misunderstood among Java developers including here on Stackoverflow. (Observe the rating of this answer for proof.)

If you have other threads running, they may not see an updated copy of the HashMap if there is no memory write out of the current thread. Memory writes occur through the use of the synchronized or volatile keywords, or through uses of some java concurrency constructs.

See Brian Goetz's article on the new Java Memory Model for details.


After a bit more looking, I found this in the java doc (emphasis mine):

Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.)

This seems to imply that it will be safe, assuming the converse of the statement there is true.


One note is that under some circumstances, a get() from an unsynchronized HashMap can cause an infinite loop. This can occur if a concurrent put() causes a rehash of the Map.

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


There is an important twist though. It's safe to access the map, but in general it's not guaranteed that all threads will see exactly the same state (and thus values) of the HashMap. This might happen on multiprocessor systems where the modifications to the HashMap done by one thread (e.g., the one that populated it) can sit in that CPU's cache and won't be seen by threads running on other CPUs, until a memory fence operation is performed ensuring cache coherence. The Java Language Specification is explicit on this one: the solution is to acquire a lock (synchronized (...)) which emits a memory fence operation. So, if you are sure that after populating the HashMap each of the threads acquires ANY lock, then it's OK from that point on to access the HashMap from any thread until the HashMap is modified again.


According to http://www.ibm.com/developerworks/java/library/j-jtp03304/ # Initialization safety you can make your HashMap a final field and after the constructor finishes it would be safely published.

... Under the new memory model, there is something similar to a happens-before relationship between the write of a final field in a constructor and the initial load of a shared reference to that object in another thread. ...


So the scenario you described is that you need to put a bunch of data into a Map, then when you're done populating it you treat it as immutable. One approach that is "safe" (meaning you're enforcing that it really is treated as immutable) is to replace the reference with Collections.unmodifiableMap(originalMap) when you're ready to make it immutable.

For an example of how badly maps can fail if used concurrently, and the suggested workaround I mentioned, check out this bug parade entry: bug_id=6423457


Be warned that even in single-threaded code, replacing a ConcurrentHashMap with a HashMap may not be safe. ConcurrentHashMap forbids null as a key or value. HashMap does not forbid them (don't ask).

So in the unlikely situation that your existing code might add a null to the collection during setup (presumably in a failure case of some kind), replacing the collection as described will change the functional behaviour.

That said, provided you do nothing else concurrent reads from a HashMap are safe.

[Edit: by "concurrent reads", I mean that there are not also concurrent modifications.

Other answers explain how to ensure this. One way is to make the map immutable, but it's not necessary. For example, the JSR133 memory model explicitly defines starting a thread to be a synchronised action, meaning that changes made in thread A before it starts thread B are visible in thread B.

My intent is not to contradict those more detailed answers about the Java Memory Model. This answer is intended to point out that even aside from concurrency issues, there is at least one API difference between ConcurrentHashMap and HashMap, which could scupper even a single-threaded program which replaced one with the other.]


http://www.docjar.com/html/api/java/util/HashMap.java.html

here is the source for HashMap. As you can tell, there is absolutely no locking / mutex code there.

This means that while its okay to read from a HashMap in a multithreaded situation, I'd definitely use a ConcurrentHashMap if there were multiple writes.

Whats interesting is that both the .NET HashTable and Dictionary<K,V> have built in synchronization code.


If the initialization and every put is synchronized you are save.

Following code is save because the classloader will take care of the synchronization:

public static final HashMap<String, String> map = new HashMap<>();
static {
  map.put("A","A");

}

Following code is save because the writing of volatile will take care of the synchronization.

class Foo {
  volatile HashMap<String, String> map;
  public void init() {
    final HashMap<String, String> tmp = new HashMap<>();
    tmp.put("A","A");
    // writing to volatile has to be after the modification of the map
    this.map = tmp;
  }
}

This will also work if the member variable is final because final is also volatile. And if the method is a constructor.

참고URL : https://stackoverflow.com/questions/104184/is-it-safe-to-get-values-from-a-java-util-hashmap-from-multiple-threads-no-modi

반응형