java.util에서 값을 가져오는 것이 안전한가?여러 스레드로부터의 해시 맵(수정 없음)
지도를 작성하는 경우가 있는데, 한번 초기화하면 다시 수정되지 않습니다.다만, 복수의 스레드로부터 액세스(get(key)만으로 액세스 됩니다.를 사용하는 것이 안전한가?java.util.HashMap
이런 식으로요?
(현재는 즐겁게 사용하고 있습니다)java.util.concurrent.ConcurrentHashMap
퍼포먼스를 향상시킬 필요는 없지만 심플한지 궁금할 뿐입니다.HashMap
충분할 거야따라서 이 질문은 "어느 것을 사용해야 하는가?"도 아니고 성능 질문도 아닙니다.오히려, 질문은 "안전할까?"이다.")
Java Memory Model의 신 Jeremy Manson은 이 토픽에 관한 3부 블로그를 가지고 있습니다.왜냐하면 본질적으로 당신은 "불변의 HashMap에 접속하는 것이 안전한가"라는 질문을 던지기 때문입니다.그 답은 "그렇다"입니다.단, "내 HashMap은 불변인가?"라는 질문에 대한 술어를 대답해야 합니다.Java에는 불변성을 판별하기 위한 비교적 복잡한 규칙 집합이 있습니다.
이 토픽에 대한 자세한 내용은 Jeremy의 블로그 투고를 참조하십시오.
자바에서의 불변성에 관한 제1부: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html
자바에서의 불변성에 관한 제2부: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html
자바에서의 불변성에 관한 제3부: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html
이디옴은 에 대한 참조가 있는 경우에만 안전합니다.HashMap
안전하게 공개됩니다.내면에 관련된 어떤 것보다HashMap
안전한 발행 자체는 구성 스레드가 맵에 대한 참조를 다른 스레드에 표시하는 방법을 다룬다.
기본적으로, 여기서 가능한 유일한 경쟁은 건설과 건설 사이입니다.HashMap
그리고 완전히 구성되기 전에 접근할 수 있는 읽기 스레드도 있습니다.대부분의 토론은 맵 객체의 상태에 어떤 일이 일어나는지에 관한 것이지만, 이는 사용자가 그것을 수정하지 않기 때문에 관련이 없습니다.따라서 유일한 흥미로운 부분은 어떻게 맵 객체의 상태를 수정하는가 하는 것입니다.HashMap
참조가 공개됩니다.
예를 들어 다음과 같이 맵을 게시한다고 가정합니다.
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
...그리고 어느 순간setMap()
맵과 함께 호출되며 다른 스레드가 를 사용하고 있습니다.SomeClass.MAP
맵에 접속하여 다음과 같은 늘을 확인합니다.
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
이것은 안전한 것처럼 보이지만 안전하지 않다.문제는 일련의 사이에 발생 전 관계가 없다는 것이다.SomeObject.MAP
다른 스레드에 대한 후속 판독이 가능하기 때문에 판독 스레드는 부분적으로 구성된 지도를 자유롭게 볼 수 있습니다.이것은 거의 모든 것을 할 수 있으며, 실제로도 읽기 스레드를 무한 루프에 넣는 것과 같은 일을 합니다.
지도를 안전하게 게시하려면 참조 기록 간에 occurrents-before 관계를 설정해야 합니다.HashMap
(즉, 출판물) 및 해당 참조(즉, 소비)의 후속 독자.쉽게[1] 기억할 수 있는 방법은 몇 가지뿐입니다.
- 올바르게 잠긴 필드를 통해 참조를 교환합니다(JLS 17.4.5).
- 정적 이니셜라이저를 사용하여 스토어 초기화(JLS 12.4)
- 휘발성 필드(JLS 17.4.5) 또는 이 규칙의 결과로 AtomicX 클래스를 통해 참조를 교환합니다.
- 값을 최종 필드(JLS 17.5)로 초기화합니다.
시나리오에서 가장 흥미로운 것은 (2), (3) 및 (4)입니다.특히 (3)은 위의 코드에 직접 적용됩니다.MAP
대상:
public static volatile HashMap<Object, Object> MAP;
그러면 모든 것이 정상입니다: 비표준 값을 보는 독자들은 반드시 스토어와 발생-전 관계를 가지고 있습니다.MAP
따라서 맵 초기화와 관련된 모든 스토어가 표시됩니다.
(2) (스태틱인테리저 사용)와 (4) (최종 사용)는 둘 다 설정할 수 없음을 의미하기 때문에 다른 메서드는 메서드의 의미를 변경합니다.MAP
실행 시 동적으로 표시됩니다.그럴 필요가 없다면 그냥 선언해MAP
로서static final HashMap<>
안전한 출판이 보장됩니다.
실제로 규칙은 "수정되지 않은 개체"에 안전하게 액세스할 수 있도록 단순합니다.
본질적으로 불변하지 않는 개체를 게시하는 경우(모든 필드에 선언됨)final
) 및:
- 선언a 시점에 할당될 개체를 이미 만들 수 있습니다.
final
필드(포함)static final
(스태틱 멤버의 경우) - 참조가 이미 표시된 후 나중에 개체를 할당하려면 휘발성b 필드를 사용하십시오.
바로 그거야!
실제로는 매우 효율적입니다.의 사용방법static final
예를 들어 필드에서는 JVM이 프로그램 수명 동안 값이 변경되지 않았다고 가정하고 크게 최적화할 수 있습니다.의 사용방법final
member 필드에서는 대부분의 아키텍처가 일반 필드 판독과 동등한 방법으로 필드를 읽을 수 있으며 추가 최적화를c 금지하지 않습니다.
마지막으로, 의 사용volatile
영향이 있습니다.많은 아키텍처(특히 x86, 읽기 통과를 허용하지 않는 아키텍처 등)에서는 하드웨어 장벽이 필요하지 않지만 컴파일 시 최적화 및 순서 변경이 이루어지지 않을 수 있습니다.그러나 이 영향은 일반적으로 작습니다.그 대신에, 실제로 요구했던 것 이상의 것을 얻을 수 있습니다.또, 안전하게 1개를 공개할 수 있을 뿐만 아니라,HashMap
, 더 많은 저장 가능HashMap
모든 독자가 안전하게 게시된 지도를 볼 수 있도록 동일한 참조를 원하는 경우.
자세한 내용은 Shipilev 또는 Manson and Goetz의 FAQ를 참조하십시오.
[1] shipilev에서 직접 인용합니다.
a 복잡한 것처럼 들리지만, 제 말은 선언 지점이나 생성자(멤버 필드) 또는 정적 이니셜라이저(정적 필드)에서 참조를 건설 시 할당할 수 있다는 것입니다.
b 옵션으로,synchronized
취득/설정 방법 또는AtomicReference
뭐 그런 것 같은데, 당신이 할 수 있는 최소한의 일을 말하는 거예요.
c 메모리 모델이 매우 약한 아키텍처(Alpha, 지금 보고 있습니다)의 경우 어떤 종류의 읽기 장벽이 필요할 수 있습니다.final
읽기 - 하지만 오늘날에는 매우 드문 경우입니다.
동기 관점에서는 읽기는 안전하지만 메모리 관점에서는 그렇지 않습니다.이는 Stackoverflow를 포함한 Java 개발자들 사이에서 널리 오해를 받고 있습니다.(증거를 위해 이 답변의 등급을 참조하십시오.)
실행 중인 다른 스레드가 있는 경우 현재 스레드에서 메모리 쓰기가 없으면 해시 맵의 업데이트된 복사본이 표시되지 않을 수 있습니다.메모리 쓰기는 synchronized 키워드 또는 volatile 키워드를 사용하거나 일부 Java 동시성 구성을 사용하여 수행됩니다.
자세한 내용은 새로운 Java 메모리 모델에 대한 Brian Goetz의 기사를 참조하십시오.
조금 더 찾아본 결과, Java doc(강조된 내 문서)에서 다음을 발견했습니다.
이 실장은 동기화되지 않습니다.여러 스레드가 동시에 해시 맵에 액세스하여 적어도1개의 스레드가 구조적으로 맵을 변경할 경우 외부에서 동기화해야 합니다.(구조 변경은 하나 이상의 매핑을 추가하거나 삭제하는 작업입니다.인스턴스에 이미 포함되어 있는 키와 관련된 값을 변경하는 것만으로 구조 변경이 아닙니다.이온)
이는 그 진술의 반대가 사실이라고 가정할 때 안전할 것이라는 것을 암시하는 것으로 보인다.
한 가지 주의할 점은 상황에 따라서는 동기화되지 않은 HashMap에서 get()이 발생하면 무한 루프가 발생할 수 있다는 것입니다.이는 동시 put()로 인해 Map의 재해시가 발생할 때 발생할 수 있습니다.
http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html
하지만 중요한 반전이 있다.맵에 액세스하는 것은 안전하지만 일반적으로 모든 스레드가 HashMap의 동일한 상태(따라서 값)를 확인하는 것은 보장되지 않습니다.이 문제는 하나의 스레드(예를 들어 HashMap을 채운 스레드)에 의해 수행된 HashMap에 대한 수정이 해당 CPU의 캐시에 저장될 수 있으며 메모리 펜스 작업이 캐시 일관성을 보장할 때까지 다른 CPU에서 실행되는 스레드에 의해 인식되지 않는 멀티프로세서 시스템에서 발생할 수 있습니다.Java Language Specification(Java 언어 사양)은 이에 대해 명시되어 있습니다.해결 방법은 메모리 펜스 조작을 실행하는 잠금(동기화(...))을 취득하는 것입니다.따라서 HashMap을 채운 후 각 스레드가 임의의 잠금을 획득하는 것이 확실하다면 그 시점부터 HashMap이 다시 수정될 때까지 임의의 스레드에서 HashMap에 액세스해도 됩니다.
http://www.ibm.com/developerworks/java/library/j-jtp03304/ # Initialization safety에 따르면 해시맵을 최종 필드로 만들 수 있으며 컨스트럭터가 완료되면 안전하게 게시됩니다.
...새로운 메모리 모델에서는 컨스트럭터의 최종 필드 기입과 다른 스레드의 해당 오브젝트에 대한 공유 참조의 초기 로드 사이에 oper-before 관계가 있습니다.
이 질문은 Brian Goetz의 "Java Concurcency in Practice" 책(목록 16.8, 350페이지)에서 다루어지고 있습니다.
@ThreadSafe
public class SafeStates {
private final Map<String, String> states;
public SafeStates() {
states = new HashMap<String, String>();
states.put("alaska", "AK");
states.put("alabama", "AL");
...
states.put("wyoming", "WY");
}
public String getAbbreviation(String s) {
return states.get(s);
}
}
부터states
로서 선언되다final
그리고 그 초기화는 소유자의 클래스 컨스트럭터 내에서 이루어지며, 다른 스레드가 맵의 내용을 수정하려고 하지 않는 한 나중에 이 맵을 읽는 모든 스레드는 컨스트럭터가 종료된 시점에서 그것을 볼 수 있습니다.
설명한 시나리오는 많은 데이터를 맵에 넣어야 하고, 데이터를 모두 채운 후에는 불변으로 간주해야 한다는 것입니다.'안전'한 접근법(즉, 실제로는 불변의 것으로 취급되도록 강제하는 것)은 참조를 다음과 같이 치환하는 것입니다.Collections.unmodifiableMap(originalMap)
불변하게 만들 준비가 되면 말이야
동시에 사용할 경우 맵이 얼마나 실패할 수 있는지와 앞서 설명한 회피책에 대해서는 bug_id=6423457의 버그 퍼레이드 엔트리를 확인해 주십시오.
단일 스레드 코드에서도 ConcurrentHashMap을 HashMap으로 교체하는 것은 안전하지 않을 수 있습니다.concurrentHashMap에서는 null을 키 또는 값으로 사용할 수 없습니다.HashMap 에서는 금지되지 않습니다(묻지마).
따라서 셋업 중에 기존 코드가 컬렉션에 늘을 추가할 가능성이 거의 없는 경우(아마도 어떤 장애의 경우)에는 설명에 따라 컬렉션을 교체하면 기능 동작이 변경됩니다.
즉, HashMap에서 동시 읽기는 안전합니다.
[편집: 동시 읽기(Current Reads)]즉, 동시 수정도 없습니다.
다른 답변에서는 이를 확인하는 방법을 설명합니다.한 가지 방법은 지도를 불변하게 만드는 것이지만, 꼭 그럴 필요는 없다.예를 들어 JSR133 메모리 모델은 스레드 B를 시작하기 전에 스레드 A에서 이루어진 변경을 스레드 B에서 볼 수 있는 동기 동작으로 명시적으로 정의한다.
제 의도는 Java 메모리 모델에 대한 보다 상세한 답변에 반박하는 것이 아닙니다.이 답변은 동시성 문제를 제외하고도 ConcurrentHashMap과 HashMap 사이에 적어도1개의 API 차이가 있음을 지적하기 위한 것입니다.이것에 의해, 1개의 스레드 프로그램이 다른 것으로 대체되는 경우도 발생할 가능성이 있습니다.
http://www.docjar.com/html/api/java/util/HashMap.java.html
다음은 HashMap의 소스입니다.보시다시피 여기에는 잠금/뮤텍스 코드가 전혀 없습니다.
즉, 멀티스레드 상황에서는 HashMap에서 읽을 수 있지만 여러 개의 쓰기가 있다면 ConcurrentHashMap을 사용하는 것이 좋습니다.
흥미로운 것은 둘 다 입니다.NET HashTable 및 사전 <K,V>에는, 동기 코드가 짜넣어져 있습니다.
초기화 및 모든 입력이 동기화되면 저장됩니다.
다음 코드는 클래스 로더가 동기화를 처리하므로 저장됩니다.
public static final HashMap<String, String> map = new HashMap<>();
static {
map.put("A","A");
}
volatile을 쓰면 동기화가 처리되기 때문에 다음 코드가 저장됩니다.
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;
}
}
final도 휘발성이기 때문에 member 변수가 final인 경우에도 이 방법은 작동합니다.그리고 만약 그 방법이 생성자라면.
언급URL : https://stackoverflow.com/questions/104184/is-it-safe-to-get-values-from-a-java-util-hashmap-from-multiple-threads-no-modi
'programing' 카테고리의 다른 글
Vue: 프로펠러 함수의 기본값 (0) | 2022.08.28 |
---|---|
반복기 없이 Set/HashSet에서 반복하는 방법 (0) | 2022.08.27 |
Storybook의 Vuex 저장소가 정의되지 않았습니다. (0) | 2022.08.27 |
(Java) 패키지 구성에 대한 베스트 프랙티스가 있습니까? (0) | 2022.08.27 |
StringUtils.isBlank() vs String.isEmpty() (0) | 2022.08.27 |