본문 바로가기
카테고리 없음

ReentrantLock 과 ConcurrentHashMap을 함께 사용해보자

by Bill Lab 2024. 12. 21.
728x90

앞의 블로그에서 언급했던것처럼 ReentrantLock 특징 중하나가 여러 쓰레드 중 동시에 락을 획득할 수 있는 

쓰레드는 오직 "하나"라는 것이다.

이를 획득하기 위해 내부적으로 직렬화된 작업이 들어가게 되고 오직 그중 한번에 하나만의 락을 취할 수 있는것이다.

 

그럼 우리가 서로 다른 목적으로 ReentrantLock 을 동시에 사용하게 되는 경우는 어떨까?

예를 들면, 상품별 재고 수량 증가과 차감을 고민해 볼 수 있다.

동일한 상품과 옵션일때만, 동시성 제어가 필요한 상황인데(청바지 M 사이즈라고 가정해보자), ReentrantLock 단독으로 사용하게 되면, 상관없는 다른 상품(티셔츠)의 수량증가도 막히게 된다.

(왜냐? 한번에 하나씩이니깐)

 

이는 비지니스 시나리오에 부합하지 않기때문에,

ConcurrentHashMap 함께 사용하면, 각각의 고유번호(ProductId or ProductOptionId)로 락관리가 가능하고

동일한 고유번호 내에서만 직렬화 처리되고 다른 고유번호의 경우 병렬처리가 가능하다

(동시에 여러 쓰레드가 락획득이 가능하게 되는 것이다.)

 

사용 방법도 간단하다.

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

public class LockPerIdExample {
 
    private static final ConcurrentHashMap<String, Integer> data = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, ReentrantLock> lockHashMap = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        //init 
        data.put("id1", 100);
        data.put("id2", 200);

        //Thread job
        Thread thread1 = new Thread(() -> updateData("id1", 2000));
        Thread thread2 = new Thread(() -> updateData("id2", 300));
        Thread thread3 = new Thread(() -> updateData("id1", 1000));

        thread1.start();
        thread2.start();
        thread3.start();

        try {
            thread1.join();
            thread2.join();
            thread3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //result
        System.out.println("Final data map: " + data);
    }
    
    private static void updateData(String id, int amount) {
        lockHashMap.putIfAbsent(id, new ReentrantLock(true));
        ReentrantLock lock = lockHashMap.get(id);
        
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " is updating " + id);
            int currentValue = data.getOrDefault(id, 0);
            data.put(id, currentValue + delta);
        } finally {
            lock.unlock();
        }
    }
}
728x90