LockSupport 는 스레드를 WAITING 상태로 변경한다.
WAITING 상태는 누가 깨워주기 전까지는 계속 대기한다. 그리고 CPU 실행 스케줄링에 들어가지 않는다.
LockSupport 를 사용하면 synchronized 의 가장 큰 단점인 무한 대기 문제를 해결할 수 있다.
public class LockSupportMainV1 {
public static void main(String[] args) {
Thread thread1 = new Thread(new ParkTest(), "Thread-1");
thread1.start();
// 잠시 대기하여 Thread-1이 park 상태에 빠질 시간을 준다.
sleep(100);
log("Thread-1 state: " + thread1.getState());
log("main -> unpark(Thread-1)");
LockSupport.unpark(thread1); // 1. unpark 사용
//thread1.interrupt(); // 2. interrupt() 사용
}
static class ParkTest implements Runnable {
@Override
public void run() {
log("park 시작");
LockSupport.park();
log("park 종료, state: " + Thread.currentThread().getState());
log("인터럽트 상태: " + Thread.currentThread().isInterrupted());
}
}
}
LockSupport.parkNanos(2000_000000); // parkNanos 사용
ReentrantLock 은 LockSupport 를 활용해서 synchronized 의 단점을 극복하면서도 매우 편리하게 임계 영역을 다룰 수 있는 다양한 기능을 제공한다
Lock 인터페이스는 동시성 프로그래밍에서 쓰이는 안전한 임계 영역을 위한 락을 구현하는데 사용된다.
package java.util.concurrent.locks;
public interface Lock {
void lock();
// 락 획득을 시도하되, 다른 스레드가 인터럽트할 수 있도록 한다.
// 대기 중에 인터럽트가 발생하면 InterruptedException 이 발생하며 락 획득을 포기한다.
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
fair mode
생성자에서 true 를 전달하면 된다. 예) new ReentrantLock(true)
공정 모드는 락을 요청한 순서대로 스레드가 락을 획득할 수 있게 한다. 이는 먼저 대기한 스레드가 먼저 락을 획득하게되어 스레드 간의 공정성을 보장한다. 그러나 이로 인해 성능이 저하될 수 있다.
non-fair mode
비공정 모드는 ReentrantLock 의 기본 모드이다. 이 모드에서는 락을 먼저 요청한 스레드가 락을 먼저 획득한다는보장이 없다.
락을 풀었을 때, 대기 중인 스레드 중 아무나 락을 획득할 수 있다.
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());
lock.lock(); // ReentrantLock 이용하여 lock을 걸기
try {
log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance);
if (balance < amount) {
log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance);
return false;
}
// 잔고가 출금액 보다 많으면, 진행
log("[검증 완료] 출금액: " + amount + ", 잔액: " + balance);
sleep(1000); // 출금에 걸리는 시간으로 가정
balance = balance - amount;
log("[출금 완료] 출금액: " + amount + ", 잔액: " + balance);
} finally {
lock.unlock(); // ReentrantLock 이용하여 lock 해제
}
log("거래 종료");
return true;
}
@Override
public int getBalance() {
lock.lock(); // ReentrantLock 이용하여 lock을 걸기
try {
return balance;
} finally {
lock.unlock(); // ReentrantLock 이용하여 lock 해제
}
}
}