GPT Archiving

ReentrantReadWriteLock

99duuk 2025. 3. 6. 15:48

네, 맞아요! ReentrantReadWriteLock에 대해 잘 이해하신 것 같네요. 코드에서 오타만 살짝 수정해서 질문에 답변해볼게요.

보완된 코드
당신이 의도한 걸 정확히 표현하면 이렇게 되겠죠:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Example {
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private Lock readLock = reentrantReadWriteLock.readLock();
    private Lock writeLock = reentrantReadWriteLock.writeLock();

    // 읽기 메서드 예시
    public void readData() {
        readLock.lock();
        try {
            System.out.println("데이터 읽기: " + Thread.currentThread().getName());
            // 읽기 작업
        } finally {
            readLock.unlock();
        }
    }

    // 쓰기 메서드 예시
    public void writeData() {
        writeLock.lock();
        try {
            System.out.println("데이터 쓰기: " + Thread.currentThread().getName());
            // 쓰기 작업
        } finally {
            writeLock.unlock();
        }
    }
}
• 오타 수정: ReetrantReadWriteLock → ReentrantReadWriteLock, 변수 이름도 일관성 있게 reentrantReadWriteLock으로 고쳤어요.

`ReentrantReadWriteLock`란?
• 정의: ReentrantReadWriteLock은 자바의 동시성 유틸리티로, **읽기 락(Read Lock)**과 **쓰기 락(Write Lock)**을 따로 제공해서 읽기와 쓰기 작업을 분리해 관리할 수 있게 해줘요.
• 특징:
◦ 읽기 락: 여러 스레드가 동시에 데이터를 읽을 수 있어요(공유 락).
◦ 쓰기 락: 한 스레드만 데이터를 쓸 수 있어요(배타적 락).
◦ 재진입 가능(Reentrant): 같은 스레드가 이미 락을 걸었다면 다시 락을 걸 수 있어요(중첩 호출 가능).

질문 확인: “읽기 쓰기가 각각 락 걸리는 거지?”
• 네, 맞아요!
• 설명:
◦ reentrantReadWriteLock.readLock()으로 얻은 readLock은 읽기 전용 락이에요.
◦ reentrantReadWriteLock.writeLock()으로 얻은 writeLock은 쓰기 전용 락이에요.
◦ 이 둘을 각각 사용하면 읽기와 쓰기에 대해 다른 동시성 제어가 적용돼요.
동작 방식
1 읽기 락 (readLock):
◦ readLock.lock()을 호출하면 여러 스레드가 동시에 읽기 작업을 할 수 있어요.
◦ 단, 쓰기 락이 걸려 있으면 읽기 락은 대기해요.
◦ 예: 10개의 스레드가 동시에 readData()를 호출해도 모두 실행 가능.
2 쓰기 �락 (writeLock):
◦ writeLock.lock()을 호출하면 그 스레드만 데이터에 접근할 수 있어요.
◦ 다른 스레드의 읽기 락이나 쓰기 락은 모두 대기해야 해요.
◦ 예: 한 스레드가 writeData()를 실행 중이면 다른 스레드는 읽기/쓰기 모두 못 함.
3 상호 작용:
◦ 읽기 락이 걸려 있는 동안: 다른 읽기 락은 가능, 쓰기 락은 불가능.
◦ 쓰기 락이 걸려 있는 동안: 읽기 �락도 쓰기 락도 불가능.

질문 맥락에서
당신이 준 WhiteListUtil 코드에서 이렇게 쓰였죠:
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

public void whiteListIp(List ipList) {
    Lock lock = this.getLockForInitWhiteList(); // writeLock
    lock.lock();
    try {
        this.whitelist = ipList;
    } finally {
        lock.unlock();
    }
}

public List getAllWhitelistIp() {
    Lock lock = this.getLockForGetAllWhitelist(); // readLock
    lock.lock();
    try {
        return this.whitelist;
    } finally {
        lock.unlock();
    }
}

private Lock getLockForInitWhiteList() {
    return reentrantReadWriteLock.writeLock();
}

private Lock getLockForGetAllWhitelist() {
    return reentrantReadWriteLock.readLock();
}
• whiteListIp(): 쓰기 락(writeLock)을 사용해서 화이트리스트를 업데이트할 때 다른 스레드가 접근하지 못하게 해요.
• getAllWhitelistIp(): 읽기 락(readLock)을 사용해서 여러 스레드가 동시에 화이트리스트를 읽을 수 있게 해요.
• 결과: 읽기와 쓰기가 각각 락으로 보호돼서, 쓰기 중에는 읽기 불가능, 읽기 중에는 여러 스레드가 동시에 가능하지만 쓰기는 대기.

테스트 예시
public static void main(String[] args) {
    Example example = new Example();

    // 읽기 스레드 여러 개
    Thread reader1 = new Thread(example::readData, "Reader1");
    Thread reader2 = new Thread(example::readData, "Reader2");
    Thread writer = new Thread(example::writeData, "Writer");

    reader1.start();
    reader2.start();
    writer.start();
}
• 출력 예시:
데이터 읽기: Reader1
• 데이터 읽기: Reader2
• 데이터 쓰기: Writer

◦ Reader1과 Reader2는 동시에 실행 가능 (읽기 락 공유).
◦ Writer는 읽기 락이 풀릴 때까지 대기했다가 실행.

정리
• 네, ReentrantReadWriteLock을 이렇게 쓰면:
◦ readLock = reentrantReadWriteLock.readLock(): 읽기 락을 걸어서 여러 스레드가 동시에 읽기 가능.
◦ writeLock = reentrantReadWriteLock.writeLock(): 쓰기 락을 걸어서 한 스레드만 쓰기 가능.
• 질문에서처럼 읽기와 쓰기가 각각 락으로 제어되는 거 맞아요!


네, 딱 맞아요! 질문에서 잘 정리하신 대로 `ReentrantReadWriteLock`을 사용하면 읽기와 쓰기를 구분해서 락을 제어할 수 있어서 기존의 단일 락 방식보다 훨씬 유연해져요. 하나씩 확인하면서 명확히 설명해볼게요.

---

### 기존 단일 락 방식 (예: `synchronized`)
- **기존 방식**:
  ```java
  public class SimpleLockExample {
      private List<String> data = new ArrayList<>();
      private final Object lock = new Object();

      public void readData() {
          synchronized (lock) {
              System.out.println("읽기: " + data);
          }
      }

      public void writeData(String value) {
          synchronized (lock) {
              data.add(value);
              System.out.println("쓰기: " + value);
          }
      }
  }
  ```
  - **특징**: `synchronized`는 단일 락(`lock`)을 사용해서, 락이 걸리면 읽기든 쓰기든 모든 작업이 대기해요.
  - **문제**:
    - 한 스레드가 `readData()`로 락을 잡으면, 다른 스레드는 `writeData()`도 `readData()`도 못 해요.
    - 읽기 작업이 많아도 동시에 처리할 수 없어서 비효율적이에요.

- **당신 말대로**: "락 하나 걸리면 아무것도 못해요." 맞아요! 읽기와 쓰기가 구분 없이 모두 블록되죠.

---

### `ReentrantReadWriteLock`으로 구분 제어
- **`ReentrantReadWriteLock` 방식**:
  ```java
  public class ReadWriteLockExample {
      private List<String> data = new ArrayList<>();
      private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
      private Lock readLock = lock.readLock();
      private Lock writeLock = lock.writeLock();

      public void readData() {
          readLock.lock();
          try {
              System.out.println("읽기: " + data);
          } finally {
              readLock.unlock();
          }
      }

      public void writeData(String value) {
          writeLock.lock();
          try {
              data.add(value);
              System.out.println("쓰기: " + value);
          } finally {
              writeLock.unlock();
          }
      }
  }
  ```
  - **읽기 락 (`readLock`)**:
    - 여러 스레드가 동시에 `readData()`를 호출해도 가능해요. 읽기 락은 공유 가능하니까요.
    - **예외**: 쓰기 락이 걸려 있으면 읽기 락도 대기해야 해요.
  - **쓰기 락 (`writeLock`)**:
    - `writeData()`는 쓰기 락을 걸어서 한 스레드만 실행돼요.
    - 쓰기 락이 걸려 있으면 다른 쓰기나 읽기 모두 대기해요.

- **당신 정리대로**:
  - "읽는 건 쓰기 락 걸려 있을 때 제외하고 언제나 락 안 걸리고": 네, 쓰기 락이 없으면 읽기 락은 여러 스레드가 동시에 걸 수 있어서 블록되지 않아요.
  - "쓰기는 쓰기 락 걸려 있을 때 대기": 네, 쓰기 락은 배타적이니까 다른 쓰기 락이 걸려 있으면 대기하고, 읽기 락이 있어도 쓰기 락이 우선 풀릴 때까지 기다려요.

---

### 차이점 비유
- **기존 단일 락 (`synchronized`)**:
  - 방에 문이 하나しかない 집이에요. 누가 들어가면(읽기든 쓰기든) 문을 잠가서 다른 사람은 다 기다려야 해요.
- **`ReentrantReadWriteLock`**:
  - 방에 읽기 전용 문(여러 명 동시에 가능)과 쓰기 전용 문(한 명만 가능)이 따로 있어요.
  - 읽기 문은 여러 사람이 동시에 드나들 수 있지만, 쓰기 문이 잠기면 모두 대기해야 해요.

---

### 동작 예시
```java
public static void main(String[] args) {
    ReadWriteLockExample example = new ReadWriteLockExample();

    Thread reader1 = new Thread(() -> example.readData(), "Reader1");
    Thread reader2 = new Thread(() -> example.readData(), "Reader2");
    Thread writer = new Thread(() -> example.writeData("New Data"), "Writer");

    reader1.start();
    reader2.start();
    writer.start();
}
```
- **출력 예시**:
  ```
  읽기: []
  읽기: []
  쓰기: New Data
  ```
  - `Reader1`과 `Reader2`는 동시에 실행 (읽기 락 공유).
  - `Writer`는 읽기 락이 풀릴 때까지 대기했다가 쓰기 락을 걸고 실행.

#### 시나리오별 정리
1. **읽기만 여러 스레드**: 모두 동시에 실행 가능.
2. **쓰기 락 걸린 상태**: 읽기 스레드와 쓰기 스레드 모두 대기.
3. **읽기 락 걸린 상태**: 다른 읽기 스레드는 가능, 쓰기 스레드는 대기.

---

### 질문 맥락에서
- `WhiteListUtil`에서:
  - `getAllWhitelistIp()` (읽기): 여러 스레드가 동시에 화이트리스트를 읽을 수 있어요.
  - `whiteListIp()` (쓰기): 화이트리스트를 업데이트할 때는 한 스레드만 가능하고, 그 동안 읽기도 대기해요.
- **이점**: 로그인 요청이 많을 때 읽기 작업(`getAllWhitelistIp`)이 자주 발생하니까, 읽기 락으로 동시에 처리해서 성능을 높일 수 있어요.

---

### 결론
- 네, 정확해요! `ReentrantReadWriteLock`으로 읽기와 쓰기를 구분하면:
  - 읽기는 쓰기 락이 걸려 있을 때만 대기하고, 나머지 경우엔 동시에 가능.
  - 쓰기는 쓰기 락이 걸려 있을 때 대기.
- 기존 단일 락보다 훨씬 효율적이죠!