고급 동기화 - concurrent.Lock
고급 동기화 - concurrent.Lock
synchronized 단점
- 무한 대기:
BLOCKED상태의 스레드는 락이 풀릴 때 까지 무한 대기 - 공정성: 락 획득을 대기 중인 스레드 중 어떤 스레드가 락을 획득할 지 알 수 없음
위 두 문제를 해결 한 것이 Lock 입니다.
LockSupport
LockSupport를 사용하면 무한 대기 상태를 해결할 수 있습니다.
기능
park(): 스레드를WAITING상태로 변경parkNanos(nanos): 스레드를 나노초 동안만TIMED_WAITING상태로 변경unpark():WAITING→RUNNABLE상태로 변경
BLOCKED: 인터럽트가 들어와도 무시하고 무한 대기WAITING: 인터럽트가 들어오면 적용
ReentrantLock
Lock인터페이스의 대표 구현체 중 하나LockSupport를 사용하여synchronized의 단점을 해결
무한 대기 문제 해결
Lock 인터페이스
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock()- 락을 획득
- 다른 스레드가 이미 락을 획득 했다면 풀릴 때 까지
WAITING상태로 대기 - 대기 중 인터럽트 응답 X
- 즉, 무한 대기 상태
lockInterruptibly()- 락을 획득
- 대기 중 인터럽트 응답 O
tryLock()- 락 획득을 시도하고, 즉시 성공 여부 반환
tryLock(long time, TimeUnit unit)- 주어진 시간 동안 락 획득 시도
- 대기 중 인터럽트 응답 O
unlock()- 락 해제
- 락을 획득한 스레드가
finally를 통해 반드시 호출
newCondition()Condition객체를 생성하여 반환Condition객체는 락과 결합하여 사용- 스레드가 특정 조건을 기다리거나 신호를 받을 수 있도록 함
lock()을 제외한 락 획득 메서드는 모두 인터럽트에 응답 하므로 무한 대기 상태에서 빠져나올수 있습니다.
여기서 사용하는 락은 모니터 락이 아닌 객체 내부의 락입니다.
공정성 문제 해결
1
2
3
4
5
// 비공정 모드 락
private final Lock nonFairLock = new ReentrantLock();
// 공정 모드 락
private final Lock fairLock = new ReentrantLock(true);
ReentrantLock 객체를 생성할 때 인자 값으로 true를 넘기면 공정 모드로 생성됩니다.
비공정 모드
- 성능 우선: 락을 획득하는 속도가 빠름
- 선점 가능: 새로운 스레드가 기존 대기 스레드 보다 먼저 락을 획득할 수 있음
- 기아 현상 가능성: 특정 스레드가 계속해서 락을 획득 못할 수 있음
공정 모드
- 성능 저하: 락을 획득하는 속도가 느림
- 공정성 보장: 대기 큐에서 먼저 대기한 스레드가 락을 먼저 획득
- 기아 현상 방지: 모든 스레드가 언젠가 락을 획득할 수 있게 보장
사용 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class BankAccountV4 implements BankAccount {
private int balance;
private final Lock lock = new ReentrantLock();
public BankAccountV4(int initialBalance) {
this.balance = initialBalance;
}
@Override
public boolean withdraw(int amount) {
log("거래 시작: " + getClass().getSimpleName());
try {
lock.lock(); // lock 걸기
// 잔고가 출금액 보다 적으면, 진행하면 안됨
log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance);
if (balance < amount) {
log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance);
return false;
}
// 잔고가 출금액 보다 많으면, 진행
log("[검증 완료] 출금액: " + amount + ", 잔액: " + balance);
sleep(1000);
balance -= amount;
log("[출금 완료] 출금액: " + amount + ", 잔액: " + balance);
} finally {
lock.unlock(); // lock 해제
}
log("거래 종료: " + getClass().getSimpleName());
return true;
}
@Override
public int getBalance() {
try {
lock.lock(); // lock 걸기
return balance;
} finally {
lock.unlock(); // lock 해제
}
}
}
참고
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.