[java] 자바 동기화 블록 대 Collections.synchronizedMap

다음 코드가 호출을 올바르게 동기화하도록 설정되어 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);


답변

동기화 한 방식이 정확합니다. 하지만 캐치가 있습니다

  1. 컬렉션 프레임 워크에서 제공하는 동기화 된 래퍼는 메서드 호출 즉, add / get / contains가 상호 배타적으로 실행되도록합니다.

그러나 실제 세계에서는 일반적으로 값을 입력하기 전에 맵을 쿼리합니다. 따라서 두 가지 작업을 수행해야하므로 동기화 된 블록이 필요합니다. 그래서 당신이 그것을 사용한 방식이 정확합니다. 하나.

  1. Collection 프레임 워크에서 사용할 수있는 Map의 동시 구현을 사용할 수 있습니다. ‘ConcurrentHashMap’혜택은

ㅏ. 동일한 작업을 수행하지만보다 효율적인 방식으로 수행하는 API ‘putIfAbsent’가 있습니다.

비. 효율성 : dThe CocurrentMap은 키를 잠그기 때문에 전체 맵의 세계를 차단하지 않습니다. 키와 값을 차단 한 위치.

씨. 코드베이스의 다른 곳에서지도 객체의 참조를 전달했을 수 있습니다. 여기서 사용자 / 다른 개발자가 잘못 사용하게 될 수 있습니다. 즉 그는지도의 객체를 잠그지 않고 모두 add () 또는 get () 할 수 있습니다. 따라서 그의 통화는 동기화 블록과 상호 배타적으로 실행되지 않습니다. 그러나 동시 구현을 사용하면 잘못 사용 / 구현 될 수 없다는 안심할 수 있습니다.


답변

체크 아웃 구글 컬렉션Multimap의 예를 들어 28 페이지의 프리젠 테이션 .

어떤 이유로 해당 라이브러리를 사용할 수없는 경우 ConcurrentHashMap대신 사용하는 것이 좋습니다 SynchronizedHashMap. putIfAbsent(K,V)요소 목록이 아직없는 경우 원자 적으로 추가 할 수 있는 멋진 방법이 있습니다. 또한 CopyOnWriteArrayList사용 패턴이 그렇게해야하는 경우 맵 값에 사용 을 고려 하십시오.