리더-라이터 상황을 위한 ReadWriteLock
캐시에서 동시 읽기를 허용하면서 배타적 쓰기를 보장하도록 ReadWriteLock을 사용합니다.
리더-라이터 상황을 위한 ReadWriteLock은(는) CoddyKit의 무료 Java Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Java Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
읽기-쓰기 문제
여러 스레드가 공유 데이터를 동시에 안전하게 읽을 수 있습니다. 하지만 쓰기에는 배타적 접근이 필요하므로 읽기나 쓰기를 동시에 수행할 수 없습니다. ReadWriteLock은 이를 모델링합니다. 여러 읽기 스레드가 동시에 접근하거나, 하나의 쓰기 스레드만 배타적으로 접근할 수 있습니다.
import java.util.concurrent.locks.*;
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();읽기 잠금: 공유 접근
어떤 스레드도 쓰기 잠금을 보유하지 않는 한 여러 스레드가 읽기 잠금을 동시에 보유할 수 있습니다:
class ReadableCache {
private final Map<String, String> cache = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
String get(String key) {
lock.readLock().lock();
try {
return cache.get(key); // concurrent reads OK
} finally {
lock.readLock().unlock();
}
}
}쓰기 잠금: 배타적 접근
한 번에 하나의 스레드만 쓰기 잠금을 보유할 수 있습니다. 쓰기 작업 중에는 모든 읽기 스레드와 다른 쓰기 스레드가 블로킹됩니다:
class WritableCache extends ReadableCache {
private final ReadWriteLock wLock = new ReentrantReadWriteLock();
private final Map<String, String> data = new HashMap<>();
void put(String key, String value) {
wLock.writeLock().lock();
try {
data.put(key, value); // exclusive write
} finally {
wLock.writeLock().unlock();
}
}
}전체 캐시 예제
읽기가 대부분이고 쓰기는 드문 스레드 안전 캐시입니다:
class Cache<K,V> {
private final Map<K,V> map = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
V get(K key) {
lock.readLock().lock();
try { return map.get(key); }
finally { lock.readLock().unlock(); }
}
void put(K key, V value) {
lock.writeLock().lock();
try { map.put(key, value); }
finally { lock.writeLock().unlock(); }
}
int size() {
lock.readLock().lock();
try { return map.size(); }
finally { lock.readLock().unlock(); }
}
}잠금 다운그레이드
ReadWriteLock은 잠금 다운그레이드를 지원합니다. 쓰기 잠금 획득 → 읽기 잠금 획득 → 쓰기 잠금 해제 순서로 진행합니다. 이를 통해 스레드는 잠금을 해제하지 않고 배타적 쓰기에서 공유 읽기로 전환할 수 있습니다:
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Lock r = rwl.readLock(), w = rwl.writeLock();
w.lock(); // acquire write
try {
// modify data
r.lock(); // acquire read WHILE holding write
} finally {
w.unlock(); // release write, now holding read only
}
try {
// safely read the just-written data
} finally {
r.unlock();
}읽기 잠금은 업그레이드되지 않음
잠금 업그레이드(읽기 → 쓰기)는 지원되지 않습니다. 읽기 잠금을 보유한 스레드가 쓰기 잠금을 획득하려고 하면 교착 상태가 발생합니다. 쓰기 잠금은 자신을 포함한 ALL 읽기 스레드가 잠금을 해제할 때까지 기다리기 때문입니다:
// DEADLOCK: Do NOT do this
lock.readLock().lock();
try {
lock.writeLock().lock(); // DEADLOCK — waits for readLock to release
} finally {
lock.readLock().unlock();
}ReadWriteLock이 유용한 경우
다음과 같은 경우 ReadWriteLock이 유용합니다:
- 읽기가 쓰기보다 훨씬 많을 때
- 읽기 작업에 상당한 시간이 걸릴 때(예: 복잡한 질의)
쓰기가 빈번하거나(쓰기 잠금이 모든 읽기를 막음) 작업이 매우 빠른 경우(두 잠금 객체의 오버헤드 때문에)에는 성능이 저하될 수 있습니다.
ReadWriteLock과 동기화 비교
동기화/ReentrantLock → 한 번에 하나의 스레드만 실행(동시에 읽는 스레드도 블로킹됨).
ReadWriteLock → 여러 읽기 스레드가 동시에 실행되고, 쓰기 스레드는 배타적으로 실행됨. 읽기가 많은 상황에서 읽기 처리량이 더 높습니다.
// synchronized: only one thread reads at a time
synchronized String get(String key) { return cache.get(key); }
// ReadWriteLock: many threads can read simultaneously
String get2(String key) {
lock.readLock().lock();
try { return cache.get(key); }
finally { lock.readLock().unlock(); }
}성능 고려 사항
두 잠금 객체를 획득하고 해제하면 잠금 하나만 사용할 때보다 오버헤드가 발생합니다. ReadWriteLock이 더 빠르다고 가정하기 전에 성능을 측정합니다. 작업 시간이 긴 읽기 중심 상황에서는 이점이 분명합니다. 빠르고 쓰기가 많은 작업 부하에서는 간단한 ReentrantLock이 더 빠를 수 있습니다.
ConcurrentHashMap 대안
간단한 맵 작업에서는 ConcurrentHashMap이 HashMap과 ReadWriteLock을 함께 사용하는 것보다 빠른 경우가 많습니다. 전역 경합 없이 내부적으로 세그먼트 수준의 잠금을 사용하기 때문입니다:
// Often better than HashMap + ReadWriteLock for simple operations:
Map<String, String> concurrent = new ConcurrentHashMap<>();
concurrent.put("key", "value"); // thread-safe, no explicit lock
String v = concurrent.get("key"); // thread-safe읽기 잠금의 공정성
기본적으로 ReentrantReadWriteLock은 비공정 방식입니다(읽기 스레드 때문에 쓰기 스레드가 기아 상태에 빠질 수 있음). 쓰기 스레드의 기아를 방지하려면 공정한 생성자를 사용합니다:
// Fair: waiting writers are served before new readers
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
System.out.println(fairLock.isFair()); // true빠른 확인
읽기가 많은 애플리케이션에서 단순한 동기화 메서드보다 ReadWriteLock이 갖는 핵심 이점은 무엇입니까?
다시 보기: ReadWriteLock
핵심 정리:
- 여러 읽기를 동시에 수행하거나 하나의 배타적 쓰기를 수행할 수 있지만, 둘 다 동시에 수행할 수는 없음
- 공유 읽기에는 readLock.lock()/unlock() 사용
- 배타적 쓰기에는 writeLock.lock()/unlock() 사용
- 잠금 다운그레이드는 지원되지만 업그레이드(읽기→쓰기)는 교착 상태를 일으킴
- 상당한 시간이 걸리는 작업이 있는 읽기 중심 상황에 적합하며, 간단한 맵에는 ConcurrentHashMap 사용
자주 묻는 질문
“리더-라이터 상황을 위한 ReadWriteLock” 강의는 무료인가요?
네 — “리더-라이터 상황을 위한 ReadWriteLock” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Java Academy 강의 전체를 잠금 해제할 수 있습니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“리더-라이터 상황을 위한 ReadWriteLock”에서 뭘 배우나요?
캐시에서 동시 읽기를 허용하면서 배타적 쓰기를 보장하도록 ReadWriteLock을 사용합니다. 브라우저에서 직접 실행하는 실습 코드로 Java Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Java Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Java Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“리더-라이터 상황을 위한 ReadWriteLock” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Java Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Java Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- ReentrantLock과 synchronized 비교
- 리더-라이터 상황을 위한 ReadWriteLock
- 원자적 변수: 잠금 없는 업데이트
- StampedLock과 낙관적 읽기