자바에서는 다양한 동시성 제어 도구를 제공하고 있다. 그중에서도 ReentrantLock, ReentrantReadWriteLock, 그리고 StampedLock은 각기 다른 용도와 성능 최적화를 위해 설계되었다. 이 세 가지 락의 주요 특징과 차이점을 정리해 보았다.
ReentrantLock은 가장 기본적인 락이며, 재진입성을 지원한다. 재진입성이란, 동일한 스레드가 이미 획득한 락을 다시 획득할 수 있음을 의미한다. 이는 중첩된 함수 호출에서 데드락을 방지하는 데 중요한 역할을 한다.
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock(); // 락 획득
try {
// 크리티컬 섹션
} finally {
lock.unlock(); // 락 해제
}
}
}
ReentrantReadWriteLock은 읽기와 쓰기 작업을 구분하여 동시성을 높이는 락이다. 여러 스레드가 동시에 읽기 작업을 수행할 수 있지만, 쓰기 작업은 단일 스레드만 수행할 수 있다.
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockExample {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
public void readData() {
readLock.lock();
try {
// 읽기 작업
} finally {
readLock.unlock();
}
}
public void writeData() {
writeLock.lock();
try {
// 쓰기 작업
} finally {
writeLock.unlock();
}
}
}
StampedLock은 자바 8에서 도입된 락으로, 낙관적 읽기(Optimistic Read), 읽기 락, 쓰기 락을 모두 지원하는 락이다. 특히, 낙관적 읽기를 통해 락을 걸지 않고도 데이터를 읽을 수 있는 방식으로 성능 최적화를 꾀한다.
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock lock = new StampedLock();
private double x, y;
// 쓰기 잠금
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}
// 낙관적 읽기
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead();
double currentX = x, currentY = y;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
| 특성 | ReentrantLock | ReentrantReadWriteLock | StampedLock |
|---|---|---|---|
| 재진입성 | 지원 (락 중복 획득 가능) | 읽기 및 쓰기 락 모두 재진입성 지원 | 지원하지 않음 (비재진입성) |
| 읽기-쓰기 구분 | 구분 없음 (읽기-쓰기 모두 동일) | 읽기와 쓰기 락 분리 | 낙관적 읽기, 읽기 락, 쓰기 락 |
| 낙관적 읽기 지원 | 지원하지 않음 | 지원하지 않음 | 지원 (락 없이 읽기 가능) |
| 공정성 | 선택 가능 (공정/비공정 모드) | 선택 가능 (공정/비공정 모드) | 지원하지 않음 |
| 성능 | 기본 락 | 읽기 작업이 많고 쓰기 작업이 적을 때 유리 | 읽기 작업이 많을 때 성능 최적화 가능 |
| 복잡성 | 비교적 간단 | 중간 | 가장 복잡 (스탬프 관리 필요) |