멀티스레드와 동시성

Ch09. 생산자 소비자 문제 - Lock Condition

webmaster 2024. 8. 24. 11:34
728x90

생산자가 생산자를 깨우고, 소비자가 소비자를 깨우는 비효율 문제를 어떻게 해결할 수 있을까?

해결 방안: 핵심은 생산자 스레드는 데이터를 생성하고, 대기 중인 소비자 스레드에게 알려주어야 한다. 반대로 소비자 스레드는 데이터를 소비하고, 대기 중인 생산자 스레드에게 알려주면 된다. 결국 생산자 스레드가 대기하는 대기 집합과, 소비자 스레드가 대기하는 대기 집합을 둘로 나누면 된다. 그리고 생산자 스레드가 데이터를 생산하면 소비자 스레드가 대기하는 대기 집합에만 알려주고, 소비자 스레드가 데이터를 소비하면 생산자 스레드가 대기하는 대기 집합에만 열려주면 되는 것이다. 이렇게 생산자용, 소비자용 대기 집합을 서로 나누어 분리하면 비효율 문제를 깔끔하게 해결할 수 있다. 그럼 대기 집합을 어떻게 분리할 수 있을까? 바로 앞서 학습한 Lock , ReentrantLock을 사용하면 된다.

 

참고: 자바는 1.0부터 존재한 synchronized "BLOCKED" 상태를 통한 임계 영역 관리의 단점을 해결하기 자바 1.5부터 Lock 인터페이스와 ReentrantLock 구현체를 제공한다.

ReentrantLock과 Condition을 사용 

실행 코드
실행 코드 - main
실행 결과 - 생산자 먼저 실행
실행 결과 - 소비자 먼저 실행
  • synchronized 대신에 Lock lock = new ReentrantLock 을 사용한다.
  • Condition
    • Condition condition = lock.newCondition()
    • Condition 은 ReentrantLock 을 사용하는 스레드가 대기하는 스레드 대기 공간이다.
    • lock.newCondition() 메서드를 호출하면 스레드 대기 공간이 만들어진다. Lock(ReentrantLock) 의 스레드 대기 공간은 이렇게 만들 수 있다.
    • 참고로 Object.wait() 에서 사용한 스레드 대기 공간은 모든 객체 인스턴스가 내부에 기본으로 가지고 있다. 반면에 Lock(ReentrantLock) 을 사용하는 경우 이렇게 스레드 대기 공간을 직접 만들어서 사용해야 한다.
  • condition.await()
    • Object.wait() 와 유사한 기능이다. 지정한 condition 에 현재 스레드를 대기("WAITING") 상태로 보관한다.
    • 이때 ReentrantLock 에서 획득한 락을 반납하고 대기 상태로 condition 에 보관된다.
  • condition.signal()
    • Object.notify() 유사한 기능이다. 지정한 condition 에서 대기중인 스레드를 하나 깨운다.
    • 깨어난 스레드는 condition 에서 빠져나온다.
실행 결과 분석 - new ReentrantLock()
  • 이 그림에서 lock 은 synchronized 에서 사용하는 객체 내부에 있는 모니터 락이 아니라, ReentrantLock 락을 뜻한다.
  • ReentrantLock 내부에 락과, 획득을 대기하는 스레드를 관리하는 대기 큐가 있다. 그림에서 스레드 대기 공간은 synchronized 에서 사용하는 스레드 대기 공간이 아니라, condition 공간을 뜻한다.
    • ReentrantLock 사용하면 condition 스레드 대기 공간이다.
  • 아직 생산자, 소비자용 스레드 대기 공간을 따로 분리하지 않았기 때문에 기존 방식과 같다고 보면 된다. 다만 구현을 synchronized 했는가 아니면 ReentrantLock 사용해서 했는가에 차이가 있을 뿐이다

 

728x90