동시성에 대해 공부하다가 알게된 ConcurrentHashMap의 내부 로직을 공부하던 도중, 동시성과 순서가 모두 중요한 경우에는 어떻게 관리해야 하지?
라는 의문점에서 시작해 공부한 내용을 정리해보고자 한다.
데이터의 순서를 유지하면서도 동시성을 보장해야 하는 상황은 현업에서 생각보다 자주 마주하게 될 것이다.꼭 만나보고 싶다
이 글에서는 이 문제를 해결하기 위한 두 가지 주요 접근 방법을 자세히 살펴보자.
첫 번째 방법은 두 개의 동시성 컬렉션을 조합하여 사용하는 접근법이다.
public class OrderedConcurrentManager<K, V> {
private final ConcurrentHashMap<K, V> dataMap = new ConcurrentHashMap<>();
private final CopyOnWriteArrayList<K> orderList = new CopyOnWriteArrayList<>();
public synchronized void put(K key, V value) {
dataMap.put(key, value);
if (!orderList.contains(key)) {
orderList.add(key);
}
}
public V get(K key) {
return dataMap.get(key);
}
public List<V> getOrderedValues() {
return orderList.stream()
.map(dataMap::get)
.collect(Collectors.toList());
}
}
장점:
단점:
두 번째 방법은 데이터를 여러 버킷으로 분할하여 관리하는 접근법이다.
public class BucketOrderedConcurrentMap<K, V> {
private static final int BUCKET_COUNT = 16;
private final OrderedBucket<K, V>[] buckets;
private static class OrderedBucket<K, V> {
private final Map<K, V> data = new LinkedHashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(K key, V value) {
lock.writeLock().lock();
try {
data.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
public V get(K key) {
lock.readLock().lock();
try {
return data.get(key);
} finally {
lock.readLock().unlock();
}
}
}
public void put(K key, V value) {
getBucket(key).put(key, value);
}
public V get(K key) {
return getBucket(key).get(key);
}
}
장점:
단점:
두 방법 모두 각자의 장단점이 있으며, 사용 상황에 따라 적절한 선택이 달라질 수 있다. 일반적으로는 두 번째 방법이 더 범용적이고 확장성이 높지만, 특수한 상황(읽기가 매우 많고 쓰기가 매우 적은 경우)에서는 첫 번째 방법이 더 단순하고 효과적일 수 있다.
두 번째 방법의 경우 ConcurrentHashMap의 동작방식과 유사하기 때문에 내부 원리를 응용했다고 봐도 무방할 것 같다!
잘 읽고 갑니다 ㅎㅎ