다음 코드가 호출을 올바르게 동기화하도록 설정되어 synchronizedMap
있습니까?
public class MyClass {
private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());
public void doWork(String key) {
List<String> values = null;
while ((values = synchronizedMap.remove(key)) != null) {
//do something with values
}
}
public static void addToMap(String key, String value) {
synchronized (synchronizedMap) {
if (synchronizedMap.containsKey(key)) {
synchronizedMap.get(key).add(value);
}
else {
List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, valuesList);
}
}
}
}
내 이해에 따르면 addToMap()
다른 스레드가 호출하지 못하도록 remove()
또는 containsKey()
호출을 받기 전에 put()
동기화 된 블록이 필요하지만 원래 맵을 만들었 doWork()
기 때문에 다른 스레드가 반환 addToMap()
전에 동기화 된 블록에 들어갈 수 없기 때문에 동기화 된 블록이 필요하지 않습니다. remove()
와 함께 Collections.synchronizedMap()
. 그 맞습니까? 이 작업을 수행하는 더 좋은 방법이 있습니까?
답변
Collections.synchronizedMap()
맵에서 실행하려는 각 원자 적 작업이 동기화되도록합니다.
그러나 맵에서 두 개 이상의 작업을 실행하려면 블록에서 동기화해야합니다. 예-올바르게 동기화하고 있습니다.
답변
JDK 6을 사용하는 경우 ConcurrentHashMap 을 확인하는 것이 좋습니다.
해당 클래스의 putIfAbsent 메서드를 확인합니다.
답변
코드에 미묘한 버그 가있을 가능성 이 있습니다 .
[ 업데이트 : 그가 map.remove ()를 사용하고 있기 때문에이 설명은 완전히 유효하지 않습니다. 나는 그 사실을 처음으로 놓쳤다. 🙁 나는대로 나머지를 떠나 있지만,이 말을 리드 문을 변경하고 있습니다. 그 지적에 대한 질문의 저자 덕분에 잠재적 버그를.]
doWork () 에서는 스레드로부터 안전한 방식으로 Map에서 List 값을 가져옵니다. 그러나 이후에 안전하지 않은 문제의 목록에 액세스하게됩니다. 예를 들어, 한 스레드는 doWork () 의 목록을 사용하고 다른 스레드는 addToMap () 에서 synchronousMap.get (key) .add (value) 를 호출 할 수 있습니다 . 이 두 액세스는 동기화되지 않습니다. 경험상의 규칙은 컬렉션의 스레드 안전 보장이 저장하는 키 또는 값으로 확장되지 않는다는 것입니다.
다음과 같이 동기화 된 목록을지도에 삽입하여이 문제를 해결할 수 있습니다.
List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list
또는 doWork () 의 목록에 액세스하는 동안지도에서 동기화 할 수 있습니다 .
public void doWork(String key) {
List<String> values = null;
while ((values = synchronizedMap.remove(key)) != null) {
synchronized (synchronizedMap) {
//do something with values
}
}
}
마지막 옵션은 동시성을 약간 제한하지만 다소 명확한 IMO입니다.
또한 ConcurrentHashMap에 대한 빠른 메모입니다. 이것은 정말 유용한 클래스이지만 동기화 된 HashMaps를 항상 적절하게 대체하는 것은 아닙니다. Javadocs에서 인용하면,
이 클래스는 스레드 안전성에 의존 하지만 동기화 세부 사항에 의존 하지 않는 프로그램에서 Hashtable과 완전히 상호 운용됩니다 .
즉, putIfAbsent ()는 원자 삽입에 적합하지만 해당 호출 중에 맵의 다른 부분이 변경되지 않을 것이라고 보장하지는 않습니다. 그것은 원 자성을 보장합니다. 샘플 프로그램에서 put () 이외의 항목에 대해 (동기화 된) HashMap의 동기화 세부 사항에 의존하고 있습니다.
마지막 것. 🙂 Java Concurrency in Practice 의이 훌륭한 인용문은 항상 디버깅 다중 스레드 프로그램을 설계하는 데 도움이됩니다.
둘 이상의 스레드에서 액세스 할 수있는 각 변경 가능한 상태 변수에 대해 해당 변수에 대한 모든 액세스는 동일한 잠금을 보유한 상태에서 수행되어야합니다.
답변
예, 올바르게 동기화하고 있습니다. 좀 더 자세히 설명하겠습니다. syncedMap 객체에 대한 메서드 호출 순서에서 후속 메서드 호출의 이전 메서드 호출 결과에 의존해야하는 경우에만 두 개 이상의 메서드 호출을 동기화해야합니다. 이 코드를 살펴 보겠습니다.
synchronized (synchronizedMap) {
if (synchronizedMap.containsKey(key)) {
synchronizedMap.get(key).add(value);
}
else {
List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, valuesList);
}
}
이 코드에서
synchronizedMap.get(key).add(value);
과
synchronizedMap.put(key, valuesList);
메서드 호출은 이전의 결과에 의존합니다.
synchronizedMap.containsKey(key)
메서드 호출.
메서드 호출 시퀀스가 동기화되지 않은 경우 결과가 잘못되었을 수 있습니다. 예를 들어, thread 1
상기 방법을 실행 addToMap()
하고 thread 2
상기 방법을 실행하는 doWork()
상의 메소드 호출 순서 synchronizedMap
로서 다음 객체 결과 :
Thread 1
상기 방법을 실행 한
synchronizedMap.containsKey(key)
결과는 ” true
“입니다. 그 운영 시스템에 실행 제어를 전환 한 후, thread 2
그것을 실행 한
synchronizedMap.remove(key)
그 후 실행 제어가로 다시 전환 thread 1
되고 예를 들어 실행되었습니다.
synchronizedMap.get(key).add(value);
믿는 synchronizedMap
목적은을 포함 key
하고 NullPointerException
있기 때문에 발생합니다 synchronizedMap.get(key)
의지 반환 null
. synchronizedMap
객체 에 대한 메서드 호출 시퀀스가 서로의 결과에 의존하지 않는 경우 시퀀스를 동기화 할 필요가 없습니다. 예를 들어 다음 시퀀스를 동기화 할 필요가 없습니다.
synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);
여기
synchronizedMap.put(key2, valuesList2);
메서드 호출은 이전 결과에 의존하지 않습니다.
synchronizedMap.put(key1, valuesList1);
메서드 호출 (일부 스레드가 두 메서드 호출 사이에 간섭을 일으키고 예를 들어를 제거했는지 여부는 중요하지 않음 key1
).
답변
나에게 맞는 것 같습니다. 변경 사항이 있으면 Collections.synchronizedMap () 사용을 중지하고 모든 것을 동일한 방식으로 동기화하여 더 명확하게합니다.
또한
if (synchronizedMap.containsKey(key)) {
synchronizedMap.get(key).add(value);
}
else {
List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, valuesList);
}
와
List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
valuesList = new ArrayList<String>();
synchronziedMap.put(key, valuesList);
}
valuesList.add(value);
답변
동기화 한 방식이 정확합니다. 하지만 캐치가 있습니다
- 컬렉션 프레임 워크에서 제공하는 동기화 된 래퍼는 메서드 호출 즉, add / get / contains가 상호 배타적으로 실행되도록합니다.
그러나 실제 세계에서는 일반적으로 값을 입력하기 전에 맵을 쿼리합니다. 따라서 두 가지 작업을 수행해야하므로 동기화 된 블록이 필요합니다. 그래서 당신이 그것을 사용한 방식이 정확합니다. 하나.
- Collection 프레임 워크에서 사용할 수있는 Map의 동시 구현을 사용할 수 있습니다. ‘ConcurrentHashMap’혜택은
ㅏ. 동일한 작업을 수행하지만보다 효율적인 방식으로 수행하는 API ‘putIfAbsent’가 있습니다.
비. 효율성 : dThe CocurrentMap은 키를 잠그기 때문에 전체 맵의 세계를 차단하지 않습니다. 키와 값을 차단 한 위치.
씨. 코드베이스의 다른 곳에서지도 객체의 참조를 전달했을 수 있습니다. 여기서 사용자 / 다른 개발자가 잘못 사용하게 될 수 있습니다. 즉 그는지도의 객체를 잠그지 않고 모두 add () 또는 get () 할 수 있습니다. 따라서 그의 통화는 동기화 블록과 상호 배타적으로 실행되지 않습니다. 그러나 동시 구현을 사용하면 잘못 사용 / 구현 될 수 없다는 안심할 수 있습니다.