developer tip

ConcurrentHashMap과 Collections.synchronizedMap (Map)의 차이점은 무엇입니까?

copycodes 2020. 10. 3. 10:58
반응형

ConcurrentHashMap과 Collections.synchronizedMap (Map)의 차이점은 무엇입니까?


동시에 여러 스레드에 의해 수정 될지도가 있습니다.

Java API에는 세 가지 동기화 된 맵 구현이있는 것 같습니다.

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

내가 이해하는 바에 따르면, 나중에 인터페이스 에 맞게 조정 Hashtable된 오래된 구현 (구식 Dictionary클래스 확장 ) Map입니다. 이 동안 되어 동기화가 심각한 것 같다 확장 성 문제를 새로운 프로젝트에 좋습니다.

하지만 다른 두 사람은 어떻습니까? Collections.synchronizedMap(Map)와에서 반환 한지도의 차이점은 무엇입니까 ConcurrentHashMap? 어느 것이 어떤 상황에 적합합니까?


필요에 따라 ConcurrentHashMap. 이를 차단하지 않고도 여러 스레드에서 맵을 동시에 수정할 수 있습니다. Collections.synchronizedMap(map)일관성을 보장하지만 성능을 저하시키는 차단 맵을 생성합니다 (올바르게 사용되는 경우).

데이터 일관성을 보장해야하고 각 스레드에 맵의 최신보기가 있어야하는 경우 두 번째 옵션을 사용하십시오. 성능이 중요하고 각 스레드가 데이터를 맵에 삽입하기 만하면 읽기가 덜 자주 발생하는 경우 첫 번째를 사용하십시오.


╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
║      Null     ║     allowed       ║              not allowed                ║
║  values/keys  ║                   ║                                         ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║Is thread-safe ║       no          ║                  yes                    ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
║  mechanism    ║    applicable     ║       map         ║                     ║ 
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

잠금 메커니즘 관련 : Hashtable 객체ConcurrentHashMap잠그고 버킷 만 잠급니다 .


에 대한 "확장 성 문제" Hashtable는 정확히 동일한 방식으로 존재 Collections.synchronizedMap(Map)합니다. 매우 간단한 동기화를 사용합니다. 즉, 하나의 스레드 만 동시에 맵에 액세스 할 수 있습니다.

이것은 간단한 삽입 및 조회가있는 경우에는별로 문제가되지 않지만 (매우 집중적으로 수행하지 않는 한) 전체 맵을 반복해야 할 때 큰 문제가되며 이는 큰 맵의 경우 시간이 오래 걸릴 수 있습니다. 한 스레드가이를 수행하고 다른 모든 스레드는 삽입하거나 조회하려면 기다려야합니다.

은 ( ConcurrentHashMap는) 매우 정교한 기술을 사용하여 동기화의 필요성을 줄이고 동기화없이 여러 스레드에 의한 병렬 읽기 액세스를 허용하며, 더 중요한 것은 Iterator동기화가 필요하지 않은를 제공하고 인터 레이션 중에 맵을 수정할 수 있도록 허용합니다. 반복 중에 삽입되지 않은 요소가 반환됩니다).


ConcurrentHashMap은 최소한 Java 5가 필요하지만 사용할 수있는 경우 선호됩니다.

여러 스레드에서 사용할 때 잘 확장되도록 설계되었습니다. 한 번에 하나의 스레드 만 맵에 액세스하면 성능이 약간 저하 될 수 있지만 여러 스레드가 동시에 맵에 액세스하면 훨씬 더 좋습니다.

필자가 철저히 추천 하는 우수한 책 Java Concurrency In Practice 의 표를 재현 블로그 항목찾았습니다 .

Collections.synchronizedMap은 TreeMap과 같은 정렬 된 맵과 같은 다른 특성으로 맵을 래핑해야하는 경우에만 의미가 있습니다.


이 두 가지의 주요 차이점 ConcurrentHashMap은 업데이트중인 데이터의 일부만 잠그고 다른 스레드에서 데이터의 다른 부분에 액세스 할 수 있다는 것입니다. 그러나 Collections.synchronizedMap()업데이트하는 동안 모든 데이터를 잠그고 다른 스레드는 잠금이 해제 될 때만 데이터에 액세스 할 수 있습니다. 업데이트 작업이 많고 읽기 작업이 상대적으로 적은 경우를 선택해야합니다 ConcurrentHashMap.

또 다른 차이점은 ConcurrentHashMap전달 된 맵의 요소 순서를 유지하지 않는다는 것입니다. HashMap데이터를 저장할 때 와 비슷합니다 . 요소 순서가 유지된다는 보장은 없습니다. 동안 Collections.synchronizedMap()보존 할지도의 요소 순서가 전달. 예를 들어, 당신이 통과하는 경우 TreeMapConcurrentHashMap, 요소는에 주문 ConcurrentHashMap의 순서와 동일하지 않을 수 TreeMap있지만, Collections.synchronizedMap()보존 할 순서.

또한 한 스레드가 맵을 업데이트하고 다른 스레드가 맵에서 얻은 반복기를 통과하는 동안 throw되는 ConcurrentHashMap것이 없음을 보장 할 수 있습니다 ConcurrentModificationException. 그러나 이에 Collections.synchronizedMap()대해서는 보장되지 않습니다.

일 이후 이 두도의 차이를 보여줍니다 ConcurrentSkipListMap.


평소와 같이 동시성 (오버 헤드) 속도 절충이 있습니다. 결정을 내리려면 애플리케이션의 세부적인 동시성 요구 사항을 고려한 다음 코드를 테스트하여 충분한 지 확인해야합니다.


에서 ConcurrentHashMap잠금이 세그먼트 대신의 전체지도에 적용됩니다. 각 세그먼트는 자체 내부 해시 테이블을 관리합니다. 잠금은 업데이트 작업에만 적용됩니다. Collections.synchronizedMap(Map)전체지도를 동기화합니다.


동기화 된지도 :

Synchronized Map은 Hashtable과 크게 다르지 않으며 동시 Java 프로그램에서 유사한 성능을 제공합니다. Hashtable과 SynchronizedMap의 유일한 차이점은 SynchronizedMap은 레거시가 아니며 Collections.synchronizedMap () 메서드를 사용하여 동기화 된 버전을 만들기 위해 모든 Map을 래핑 할 수 있다는 것입니다.

ConcurrentHashMap :

ConcurrentHashMap 클래스는 표준 HashMap의 동시 버전을 제공합니다. 이것은 Collections 클래스에서 제공되는 synchronousMap 기능의 개선입니다.

Hashtable 및 Synchronized Map과 달리 전체 Map을 잠그지 않고 대신 세그먼트로 맵을 분할하고 잠금을 수행합니다. 판독기 스레드 수가 작성기 스레드 수보다 크면 성능이 더 좋습니다.

ConcurrentHashMap은 기본적으로 16 개의 영역으로 구분되며 잠금이 적용됩니다. 이 기본 번호는 ConcurrentHashMap 인스턴스를 초기화하는 동안 설정할 수 있습니다. 특정 세그먼트에 데이터를 설정하면 해당 세그먼트에 대한 잠금이 획득됩니다. 즉, 각각 별도의 버킷에 영향을 미치는 경우 두 개의 업데이트를 동시에 안전하게 실행할 수 있으므로 잠금 경합을 최소화하고 성능을 최대화 할 수 있습니다.

ConcurrentHashMap은 ConcurrentModificationException을 발생시키지 않습니다.

ConcurrentHashMap은 한 스레드가 수정을 시도하고 다른 스레드가 반복하는 경우 ConcurrentModificationException을 발생시키지 않습니다.

synchornizedMap과 ConcurrentHashMap의 차이점

Collections.synchornizedMap (HashMap)은 Hashtable과 거의 동일한 컬렉션을 반환합니다. 여기서 Map의 모든 수정 작업은 Map 객체에 잠기지 만 ConcurrentHashMap의 경우 동시성 수준에 따라 전체 Map을 다른 파티션으로 분할하여 스레드 안전성을 달성합니다. 전체 맵을 잠그는 대신 특정 부분 만 잠급니다.

ConcurrentHashMap은 null 키 또는 null 값을 허용하지 않지만 동기화 된 HashMap은 하나의 null 키를 허용합니다.

유사한 링크

Link1

Link2

성능 비교


에 대해 맞습니다 HashTable. 잊을 수 있습니다.

귀하의 기사 에서는 HashTable 및 동기화 된 래퍼 클래스가 한 번에 하나의 스레드 만 맵에 액세스 할 수 있도록 허용하여 기본 스레드 안전성을 제공하지만 많은 복합 작업에 여전히 추가 동기화가 필요하기 때문에 이것이 '진정한'스레드 안전성이 아니라는 사실을 언급합니다. 예:

synchronized (records) {
  Record rec = records.get(id);
  if (rec == null) {
      rec = new Record(id);
      records.put(id, rec);
  }
  return rec;
}

그러나 위와 같이 일반적인 블록에 ConcurrentHashMap대한 간단한 대안 이라고 생각하지 마십시오 . 기사를 읽고 그 복잡성을 더 잘 이해하십시오.HashMapsynchronized


다음은 몇 가지입니다.

1) ConcurrentHashMap은 Map의 일부만 잠그지 만 SynchronizedMap은 전체 MAp를 잠급니다.
2) ConcurrentHashMap은 SynchronizedMap보다 성능이 우수하고 확장 성이 뛰어납니다.
3) 여러 독자와 단일 작성자의 경우 ConcurrentHashMap이 최선의 선택입니다.

이 텍스트는 Java의 ConcurrentHashMap과 해시 테이블의 차이점 에서 가져온 것입니다.


ConcurrentHashMap 및 synchronisedHashmap 및 Hashtable을 사용하여 스레드 안전성을 달성 할 수 있습니다. 그러나 아키텍처를 보면 많은 차이가 있습니다.

  1. synchronisedHashmap 및 Hashtable

둘 다 객체 수준에서 잠금을 유지합니다. 따라서 put / get과 같은 작업을 수행하려면 먼저 잠금을 획득해야합니다. 동시에 다른 스레드는 어떤 작업도 수행 할 수 없습니다. 따라서 한 번에 하나의 스레드 만이 작업을 수행 할 수 있습니다. 따라서 여기에서 대기 시간이 늘어납니다. ConcurrentHashMap과 비교할 때 성능이 상대적으로 낮다고 말할 수 있습니다.

  1. ConcurrentHashMap

세그먼트 수준에서 잠금을 유지합니다. 16 개의 세그먼트가 있으며 기본적으로 동시성 수준을 16으로 유지합니다. 따라서 한 번에 16 개의 스레드가 ConcurrentHashMap에서 작동 할 수 있습니다. 또한 읽기 작업에는 잠금이 필요하지 않습니다. 따라서 여러 스레드에서 get 작업을 수행 할 수 있습니다.

thread1이 세그먼트 2에 넣기 작업을 수행하고 thread2가 세그먼트 4에 넣기 작업을 수행하려는 경우 여기에서 허용됩니다. 즉, 16 개의 쓰레드가 ConcurrentHashMap에서 한번에 업데이트 (put / delete) 작업을 수행 할 수 있습니다.

여기에서 대기 시간이 줄어들 것입니다. 따라서 성능은 synchronisedHashmap 및 Hashtable보다 상대적으로 좋습니다.


ConcurrentHashMap

  • 프로젝트에서 매우 높은 동시성이 필요할 때 ConcurrentHashMap을 사용해야합니다.
  • 전체 맵을 동기화하지 않고도 스레드로부터 안전합니다.
  • 쓰기가 잠금으로 수행되는 동안 읽기가 매우 빠르게 발생할 수 있습니다.
  • 개체 수준에서 잠금이 없습니다.
  • 잠금은 해시 맵 버킷 수준에서 훨씬 더 세분화됩니다.
  • ConcurrentHashMap은 한 스레드가 수정을 시도하고 다른 스레드가 반복하는 경우 ConcurrentModificationException을 발생시키지 않습니다.
  • ConcurrentHashMap은 많은 잠금을 사용합니다.

SynchronizedHashMap

  • 개체 수준에서 동기화.
  • 모든 읽기 / 쓰기 작업은 잠금을 획득해야합니다.
  • 전체 컬렉션을 잠그면 성능 오버 헤드가 발생합니다.
  • 이것은 본질적으로 전체 맵에 대한 하나의 스레드에만 액세스를 제공하고 다른 모든 스레드를 차단합니다.
  • 경합이 발생할 수 있습니다.
  • SynchronizedHashMap은 동시 수정시 빠르게 실패하는 Iterator를 반환합니다.

출처


ConcurrentHashMap은 동시 액세스에 최적화되어 있습니다.

Accesses don't lock the whole map but use a finer grained strategy, which improves scalability. There are also functional enhanvements specifically for concurrent access, e.g. concurrent iterators.


There is one critical feature to note about ConcurrentHashMap other than concurrency feature it provides, which is fail-safe iterator. I have seen developers using ConcurrentHashMap just because they want to edit the entryset - put/remove while iterating over it. Collections.synchronizedMap(Map) does not provide fail-safe iterator but it provides fail-fast iterator instead. fail-fast iterators uses snapshot of the size of map which can not be edited during iteration.


  1. If Data Consistency is highly important - Use Hashtable or Collections.synchronizedMap(Map).
  2. If speed/performance is highly important and Data Updating can be compromised- Use ConcurrentHashMap.

In general, if you want to use the ConcurrentHashMap make sure you are ready to miss 'updates'
(i.e. printing contents of the HashMap does not ensure it will print the up-to-date Map) and use APIs like CyclicBarrier to ensure consistency across your program's lifecycle.


Collections.synchronizedMap() method synchronizes all the methods of the HashMap and effectively reduces it to a data structure where one thread can enter at a time because it locks every method on a common lock.

In ConcurrentHashMap synchronization is done a little differently. Rather than locking every method on a common lock, ConcurrentHashMap uses separate lock for separate buckets thus locking only a portion of the Map. By default there are 16 buckets and also separate locks for separate buckets. So the default concurrency level is 16. That means theoretically any given time 16 threads can access ConcurrentHashMap if they all are going to separate buckets.


Besides what has been suggested, I'd like to post the source code related to SynchronizedMap.

To make a Map thread safe, we can use Collections.synchronizedMap statement and input the map instance as the parameter.

The implementation of synchronizedMap in Collections is like below

   public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

As you can see, the input Map object is wrapped by the SynchronizedMap object.
Let's dig into the implementation of SynchronizedMap ,

 private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        public Set<K> keySet() {
            synchronized (mutex) {
                if (keySet==null)
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                return keySet;
            }
        }

        public Set<Map.Entry<K,V>> entrySet() {
            synchronized (mutex) {
                if (entrySet==null)
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                return entrySet;
            }
        }

        public Collection<V> values() {
            synchronized (mutex) {
                if (values==null)
                    values = new SynchronizedCollection<>(m.values(), mutex);
                return values;
            }
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return m.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return m.hashCode();}
        }
        public String toString() {
            synchronized (mutex) {return m.toString();}
        }

        // Override default methods in Map
        @Override
        public V getOrDefault(Object k, V defaultValue) {
            synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
        }
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            synchronized (mutex) {m.forEach(action);}
        }
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            synchronized (mutex) {m.replaceAll(function);}
        }
        @Override
        public V putIfAbsent(K key, V value) {
            synchronized (mutex) {return m.putIfAbsent(key, value);}
        }
        @Override
        public boolean remove(Object key, Object value) {
            synchronized (mutex) {return m.remove(key, value);}
        }
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            synchronized (mutex) {return m.replace(key, oldValue, newValue);}
        }
        @Override
        public V replace(K key, V value) {
            synchronized (mutex) {return m.replace(key, value);}
        }
        @Override
        public V computeIfAbsent(K key,
                Function<? super K, ? extends V> mappingFunction) {
            synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
        }
        @Override
        public V computeIfPresent(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
        }
        @Override
        public V compute(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.compute(key, remappingFunction);}
        }
        @Override
        public V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.merge(key, value, remappingFunction);}
        }

        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

What SynchronizedMap does can be summarized as adding a single lock to primary method of the input Map object. All method guarded by the lock can't be accessed by multiple threads at the same time. That means normal operations like put and get can be executed by a single thread at the same time for all data in the Map object.

It makes the Map object thread safe now but the performance may become an issue in some scenarios.

The ConcurrentMap is far more complicated in the implementation, we can refer to Building a better HashMap for details. In a nutshell, it's implemented taking both thread safe and performance into consideration.

참고URL : https://stackoverflow.com/questions/510632/whats-the-difference-between-concurrenthashmap-and-collections-synchronizedmap

반응형