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}
}
}