운영체제

concurrent Lock

으엉어엉 2024. 12. 16. 14:07
728x90

synchronized 단점

  • 무한 대기 :  BLOCKED 상태의 스레드는 락이 풀릴 때 까지 무한 대기한다.
    • 특정 시간까지만 대기하는 타임아웃X
    • 중간에 인터럽트X
  • 공정성: : 락이 돌아왔을 때 BLOCKED 상태의 여러 스레드 중에 어떤 스레드가 락을 획득할 지 알 수 없다. 최악의 경우 특정 스레드가 너무 오랜기간 락을 획득하지 못할 수 있다.

 

LockSupport 를 사용하면 synchronized 의 가장 큰 단점인 무한 대기 문제를 해결할 수 있다.

 

 

LockSupport 는 스레드를 WAITING로 변경하는데 WAITING이란 상태는 누가 깨워주기 전까지는 계속 대기한다. 그리고 CPU 실행 스케줄링에 들어가지 않는다.

  • park() : : 스레드를 WAITING 상태로 변경한다.
  • parkNanos(nanos) : 스레드를 나노초 동안만 TIMED_WAITING 상태로 변경한다.
  • unpark(thread): WAITING 상태의 대상 Thread를 RUNNABLE 상태로 변경한다. 

 

인터럽트 사용

WAITING 상태의 스레드에 인터럽트가 발생하면 WAITING 상태에서 RUNNABLE 상태로 변하면서 깨어난다.

 

시간 대기

스레드를 특정 시간 동안만 대기하는 parkNanos(nanos): : 스레드를 나노초 동안만 TIMED_WAITING 상태로 변경한다. 지정한 나노초가 지나면 TIMED_WAITING 상태에서 빠져나와서 RUNNABLE 상태로 변경된다,

BLOCKED vs WAITING

WAITING 상태에 특정 시간까지만 대기하는 기능이 포함된 것이다.

인터럽트

BLOCKED 상태는 인터럽트가 걸려도 대기 상태를 빠져나오지 못한다. 여전히 BLOCKED 상태이다. synchronized에서 락을 획득하기 위해 대기할 때 사용된다.

WAITING 상태는 인터럽트가 걸리면 대기 상태를 빠져나온다. 그래서 RUNNABLE 상태로 변한다. WAITING  상태는 스레드가 특정 조건이나 시간 동안 대기할 때 발생하는 상태이다.

 

하지만 이런 기능을 직접 구현하기는 매우 어렵다. 예를 들어 스레드 10개를 동시에 실행했는데, 그중에 딱 1개의 스레 드만 락을 가질 수 있도록 락 기능을 만들어야 한다. 그리고 나머지 9개의 스레드가 대기해야 하는데, 어떤 스레드가 대 기하고 있는지 알 수 있는 자료구조가 필요하다. 그래야 이후에 대기 중인 스레드를 찾아서 깨울 수 있다. 여기서 끝이 아니다. 대기 중인 스레드 중에 어떤 스레드를 깨울지에 대한 우선순위 결정도 필요하다.

LockSupport는 저수준이라 더 고수준인 ReentrantLock을 사용한다.

 

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

void lockInterruptibly() : 락 획득을 시도하되, 다른 스레드가 인터럽트할 수 있도록 한다. 만약 다른 스레드가 이미 락을 획득했다면, 현재 스레드는 락을 획득할 때까지 대기한다. 대기 중에 인터럽트가 발생하면 InterruptedException 이 발생하 며 락 획득을 포기한다.

 

boolean tryLock(): 락 획득을 시도하고, 즉시 성공 여부를 반환한다. 만약 다른 스레드가 이미 락을 획득했다면 false 를 반환하고, 그렇지 않으면 락을 획득하고 true 를 반환한다. 대기가 없으면 들어가고 대기줄이 있으면 포기한다.

 

boolean tryLock(long time, TimeUnit unit): 주어진 시간 동안 락 획득을 시도한다. 주어진 시간 안에 락을 획득하면 true 를 반환한다. 주어진 시간이 지나도 락을 획득하지 못한 경우 false 를 반환한다. 이 메서드는 대기 중 인터럽트가 발생하면 InterruptedException 이 발생하며 락 획득을 포기한다. 특정 시간 만큼만 기다리는 것이다.

 

void unlock(): 락을 해제한다. 락을 해제하면 락 획득을 대기 중인 스레드 중 하나가 락을 획득할 수 있게 된다. 락을 획득한 스레드가 호출해야 하며, 그렇지 않으면 IllegalMonitorStateException 이 발생할 수 있다.

 

Condition newCondition(): Condition 객체를 생성하여 반환한다.  Condition 객체는 락과 결합되어 사용되며, 스레드가 특정 조건을 기다리거나 신호를 받을 수 있도록 한다

 

 

이 메서드들을 사용하면 고수준의 동기화 기법을 구현할 수 있다.  Lock 인터페이스는 synchronized 블록보다 더 많은 유연성을 제공하며, 특히 락을 특정 시간 만큼만 시도하거나, 인터럽트 가능한 락을 사용할 때 유용하다. 이 메서드들을 보면 알겠지만 다양한 메서드를 통해  synchronized 의 단점인 무한 대기 문제도 깔끔하게 해결할 수 있다.

 

공정성 : Lock 인터페이스가 제공하는 다양한 기능 덕분에 synchronized 의 단점인 무한 대기 문제가 해결되었다. 락이 돌아왔을 때 BLOCKED 상태의 여러 스레드 중에 어떤 스레드가 락을 획득할 지 알 수 없다. 최악의 경우 특정 스레드가 너무 오랜기간 락을 획득하지 못할 수 있다.

 

공정 모드 (Fair mode) 오래 기다리면 먼저 들어가는 것이다. new ReentrantLock(true)로 생성자때 true를 넣어주면 된다.

 

@Override
public boolean withdraw(int amount) {
    log("거래 시작: " + getClass().getSimpleName());
    lock.lock(); // ReentrantLock 이용하여 lock을 걸기
    //lock 걸고 나면 try, finally를 하여 무조건 unlock이 호출 되어야 한다.
    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();
    }
    log("거래 종료");
    return true;
}

// ReentrantLock 이용하여 lock 해제
@Override
public int getBalance() {
    lock.lock();
    // ReentrantLock 이용하여 lock 걸기
    try {
        return balance;
    } finally {
        lock.unlock();// ReentrantLock 이용하여 lock 해제
    }
}

 

ReentrantLock - 대기 중단

ReentrantLock 을 사용하면 락을 무한 대기하지 않고, 중간에 빠져나오는 것이 가능하다. 심지어 락을 얻을 수 없다면 기다리지 않고 즉시 빠져나오는 것도 가능하다. boolean tryLock(long time, TimeUnit unit)

728x90

'운영체제' 카테고리의 다른 글

원자적 연산  (2) 2024.12.20
생산자 소비자 문제  (0) 2024.12.18
Synchronized  (0) 2024.12.15
Volatile - 메모리가시성  (0) 2024.12.15
Thread - interrupt  (0) 2024.12.14