Pink Spider/ReentrantLock & synchronized

Created Sun, 20 Apr 2025 18:15:45 +0900 Modified Mon, 08 Dec 2025 08:41:47 +0900
948 Words 4 min

ReentrantLock은 Java에서 제공하는 고급 동기화 도구로, synchronized 블록보다 더 많은 제어를 제공하는 Lock 인터페이스의 구현체입니다.


🔐 ReentrantLock이란?

  • Java의 java.util.concurrent.locks.ReentrantLock 클래스
  • 재진입 가능(reentrant) 한 락
    같은 스레드가 여러 번 lock을 획득해도 deadlock 없이 동작
  • synchronized와 유사하지만 더 세밀한 컨트롤 가능

🔸 기본 사용 예제

import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock(); // 🔐 lock 획득
        try {
            count++;
        } finally {
            lock.unlock(); // 🔓 꼭 해제해줘야 함!
        }
    }

    public int getCount() {
        return count;
    }
}

⚠️ finally 블록에서 unlock()을 꼭 호출해야 합니다.
예외가 발생해도 락이 풀리지 않으면 데드락(deadlock) 발생할 수 있어요.


🔄 재진입 예시

public void outer() {
    lock.lock();
    try {
        inner(); // 같은 스레드가 다시 lock 획득 가능
    } finally {
        lock.unlock();
    }
}

public void inner() {
    lock.lock();
    try {
        // 작업
    } finally {
        lock.unlock();
    }
}

→ 같은 스레드가 outer()inner()에서 중복으로 lock을 걸어도 OK
(이게 바로 Reentrant = 재진입 가능!)


🔧 synchronized vs ReentrantLock

항목 synchronized ReentrantLock
재진입 가능 ✅ 가능 ✅ 가능
락 해제 자동 (JVM이 해제) 수동 (unlock() 직접 호출)
조건 대기 ❌ 불가능 (wait/notify 필요) Condition으로 정교하게 가능
인터럽트 가능 lockInterruptibly() 가능
시도(lock 시도) tryLock()으로 가능
성능 가벼움 (간단한 경우 유리) 고급 제어 가능 (복잡한 상황 유리)

🧠 요약

  • ReentrantLock = lock을 더 세밀하게 제어하고 싶을 때 쓰는 도구
  • synchronized보다 유연하지만, 직접 unlock 해야 한다는 점 유의
  • 멀티스레드 환경에서 성능 최적화, 타임아웃 처리, 인터럽트 대응 등에 유리

이제 진짜 멀티스레드 환경에서도 안정적으로 동작하고, 성능도 괜찮은 ReentrantLock 기반의 고급 LRU 캐시를 만들어보겠습니다.


✅ ReentrantLock 기반 고급 LRUCache

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

public class AdvancedLRUCache<K, V> {
    private final int capacity;
    private final Map<K, V> map;
    private final ReentrantLock lock = new ReentrantLock();

    public AdvancedLRUCache(int capacity) {
        this.capacity = capacity;
        // LinkedHashMap with access-order enabled
        this.map = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > AdvancedLRUCache.this.capacity;
            }
        };
    }

    public V get(K key) {
        lock.lock();
        try {
            return map.get(key); // 접근 순서 갱신됨
        } finally {
            lock.unlock();
        }
    }

    public void put(K key, V value) {
        lock.lock();
        try {
            map.put(key, value);
        } finally {
            lock.unlock();
        }
    }

    public boolean containsKey(K key) {
        lock.lock();
        try {
            return map.containsKey(key);
        } finally {
            lock.unlock();
        }
    }

    public int size() {
        lock.lock();
        try {
            return map.size();
        } finally {
            lock.unlock();
        }
    }

    public String toString() {
        lock.lock();
        try {
            return map.toString();
        } finally {
            lock.unlock();
        }
    }
}

🔍 특징 요약

기능 설명
LinkedHashMap 사용 LRU 순서 유지 (access-order)
ReentrantLock 사용 멀티스레드 환경에서도 안전
removeEldestEntry() 자동으로 가장 오래된 항목 제거
커스터마이징 쉬움 만약 TTL(만료 시간)이나 size 정책을 더하고 싶다면 추가 가능

✅ 사용 예시

public class Main {
    public static void main(String[] args) {
        AdvancedLRUCache<Integer, String> cache = new AdvancedLRUCache<>(3);
        cache.put(1, "One");
        cache.put(2, "Two");
        cache.put(3, "Three");
        System.out.println(cache); // {1=One, 2=Two, 3=Three}

        cache.get(1); // 1이 가장 최근
        cache.put(4, "Four"); // 2가 제거됨

        System.out.println(cache); // {3=Three, 1=One, 4=Four}
    }
}