ReentrantReadWriteLock
네, 맞아요! 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`으로 읽기와 쓰기를 구분하면:
- 읽기는 쓰기 락이 걸려 있을 때만 대기하고, 나머지 경우엔 동시에 가능.
- 쓰기는 쓰기 락이 걸려 있을 때 대기.
- 기존 단일 락보다 훨씬 효율적이죠!