your programing

자바 동기화 블록 대 Collections.synchronizedMap

lovepro 2020. 10. 5. 20:30
반응형

자바 동기화 블록 대 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)

메서드 호출.

If the sequence of method calls were not synchronized the result might be wrong. For example thread 1 is executing the method addToMap() and thread 2 is executing the method doWork() The sequence of method calls on the synchronizedMap object might be as follows: Thread 1 has executed the method

synchronizedMap.containsKey(key)

and the result is "true". After that operating system has switched execution control to thread 2 and it has executed

synchronizedMap.remove(key)

After that execution control has been switched back to the thread 1 and it has executed for example

synchronizedMap.get(key).add(value);

believing the synchronizedMap object contains the key and NullPointerException will be thrown because synchronizedMap.get(key) will return null. If the sequence of method calls on the synchronizedMap object is not dependent on the results of each other then you don't need to synchronize the sequence. For example you don't need to synchronize this sequence:

synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);

Here

synchronizedMap.put(key2, valuesList2);

method call does not rely on the results of the previous

synchronizedMap.put(key1, valuesList1);

method call (it does not care if some thread has interfered in between the two method calls and for example has removed the key1).


That looks correct to me. If I were to change anything, I would stop using the Collections.synchronizedMap() and synchronize everything the same way, just to make it clearer.

Also, I'd replace

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

with

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

Check out Google Collections' Multimap, e.g. page 28 of this presentation.

If you can't use that library for some reason, consider using ConcurrentHashMap instead of SynchronizedHashMap; it has a nifty putIfAbsent(K,V) method with which you can atomically add the element list if it's not already there. Also, consider using CopyOnWriteArrayList for the map values if your usage patterns warrant doing so.


The way you have synchronized is correct. But there is a catch

  1. Synchronized wrapper provided by Collection framework ensures that the method calls I.e add/get/contains will run mutually exclusive.

However in real world you would generally query the map before putting in the value. Hence you would need to do two operations and hence a synchronized block is needed. So the way you have used it is correct. However.

  1. You could have used a concurrent implementation of Map available in Collection framework. 'ConcurrentHashMap' benefit is

a. It has a API 'putIfAbsent' which would do the same stuff but in a more efficient manner.

b. Its Efficient: dThe CocurrentMap just locks keys hence its not blocking the whole map's world. Where as you have blocked keys as well as values.

c. You could have passed the reference of your map object somewhere else in your codebase where you/other dev in your tean may end up using it incorrectly. I.e he may just all add() or get() without locking on the map's object. Hence his call won't run mutually exclusive to your sync block. But using a concurrent implementation gives you a peace of mind that it can never be used/implemented incorrectly.

참고URL : https://stackoverflow.com/questions/567068/java-synchronized-block-vs-collections-synchronizedmap

반응형