다음 코드가 호출을 올바르게 동기화하도록 설정되어 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 () 할 수 있습니다. 따라서 그의 통화는 동기화 블록과 상호 배타적으로 실행되지 않습니다. 그러나 동시 구현을 사용하면 잘못 사용 / 구현 될 수 없다는 안심할 수 있습니다.