LockSupport

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

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 해제
        }
    }
}