0Pricing
Java Academy · 강의

리더-라이터 상황을 위한 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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. ReentrantLock과 synchronized 비교
  2. 리더-라이터 상황을 위한 ReadWriteLock
  3. 원자적 변수: 잠금 없는 업데이트
  4. StampedLock과 낙관적 읽기
← Java Academy(으)로 돌아가기