여러 작업(스레드, 프로세스 등)이 동시에 공유자원에 접근할 때, 한 번에 하나만 접근할 수 있도록 제어하는 매커니즘
멀티스레스 환경에서는 여러 스레드가 동시에 같은 데이터를 조작할 수 있다. 이 때 순서에 따라 결과가 달라지는 문제(Race Condition)가 발생할 수 있기 때문에, 한 번에 하나의 스레드만 작업하도록 막아야 한다.
| 종류 | 설명 | 예시 |
|---|---|---|
| Mutual Exclusion(상호 배제) | 가장 기본적인 락. 한 번에 하나만 접근 가능 | synchronized, ReentrantLock |
| Read-Write Lock | 읽기는 여러 개 허용, 쓰기는 하나만 허용 | ReentrantReadWriteLock |
| Optimistic Lock(낙관적 락) | 락 안 걸고 먼저 작업한 후 충돌시 실패 처리 | DB에서 버전 필드(version)를 사용 |
| Pessimistic Lock(비관적 락) | 접근 자체를 막아 충돌 방지 | DB 트랜젝션에서 SELECT FOR UPDATE |
Pessimistic Lock(비관적 락)
SELECT FOR UPDATE 구문으로 구현SELECT * FROM orders WHERE id = 1 FOR UPDATE;
Optimistic Lock(낙관적 락)
if (currentVersion == dbVersion) {
update();
} else {
throw new OptimisticLockingFailureException();
}
Mutex, Mutual Exclusion (상호배제 락)
synchronized, ReentrantLock이 대표적Shared Lock, Read Lock (공유 락)
ReentrantReadWriteLock의 readLock()Exclusive Lock, Write Lock (배타 락)
ReentrantReadWriteLock의 writeLock()Fair Lock (공정 락)
ReentrantLock lock = new ReentrantLock(true); // 공정 락
Non-Fair Lock (비공정 락)
ReentrantLock lock = new ReentrantLock(false); // 비공정 락
Spin Lock (스핀 락)
세마포어 (Semaphore)
Semaphore 클래스로 제공Semaphore semaphore = new Semaphore(5); // 5개까지 동시에 접근 허용
Read-Write Lock
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock(); // 읽기 락
lock.writeLock().lock(); // 쓰기 락
정리
| 종류 | 특징 | 사용 예시 |
|---|---|---|
| 비관적 락 | 무조건 락, 충돌 방지 | DB SELECT FOR UPDATE |
| 낙관적 락 | 락 없이 작업, 나중 검증 | 버전 필드 검사 |
| 상호 배제 락 | 한번에 하나만 접근 | synchronized, ReentrantLock |
| 공유 락 | 읽기 동시 허용 | ReentrantReadWriteLock.readLock() |
| 배타 락 | 쓰기 단독 허용 | ReentrantReadWriteLock.writeLock() |
| 공정 락 | 요청 순서 보장 | ReentrantLock(true) |
| 비공정 락 | 순서 무시, 속도 빠름 | ReentrantLock(false) |
| 스핀 락 | 대기 중 락 계속 시도 | 짧은 락 점유 환경 |
| 세마포어 | 동시 접근 개수 제한 | 커넥션 풀 |
| 리드-라이트 | 읽기 다수, 쓰기 단일 | 다중 읽기 서비스 |
public class DeadlockExample {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void method1() {
synchronized (lockA) {
System.out.println("Thread 1: lockA 획득");
synchronized (lockB) {
System.out.println("Thread 1: lockB 획득");
}
}
}
public void method2() {
synchronized (lockB) {
System.out.println("Thread 2: lockB 획득");
synchronized (lockA) {
System.out.println("Thread 2: lockA 획득");
}
}
}
}
Thread 1이 lockA를 잡고 lockB를 기다림
Thread 2는 lockB를 잡고 lockA를 기다림
서로 상대방의 락 해제를 기다리며 무한 대기
위의 네 가지 조건이 충족되면 Deadlock이 발생할 수 있음
ReentrantLock lock = new ReentrantLock(true) // 요청 순서대로 락 획득
락은 동시성 문제를 해결하기 위한 도구로, 상황에 따라 비관적/낙관적, 공유/배타, 공정/비공정 락 등을 선택해서 사용한다