설명 순서는 다음과 같습니다.

1. Monitor 방법
2. Java에서 Monitor 방법
3. 예시를 보며 이해
4. Java에서 Lock & Condition 방법

1. Monitor 방법

   - Java는 프로그래밍 레벨에서 동기화 문제 해결을 돕기 위해, Monitor 방법을 채택해서 제공

   - Monitor의 전체적인 흐름 : Lock을 획득 -> 임계영역 진입 -> Lock반환. Lock을 획득하지 못할 경우 잠시 대기 상태에 들어가고, 누군가 Lock을 반환할 때 스레드를 깨우는 방법

   - Monitor의 구조

      ① 임계영역 : 한 스레드만 들어가서 실행 가능한 영역

      ② Entry Set : Lock을 얻기위해 대기하는 영역

      ③ Wait Set : Lock을 얻었지만, 실행할 수 있는 조건에 맞지 않아 Lock을 반납하고 대기하는 영역


2. Java에서 Monitor 방법

   - Java 객체는 내부적으로 하나의 Monitor를 가짐

   - 동기화를 위한 키워드

      ① synchronized : 메서드 또는 블럭 앞에 붙임. 이 메서드 또는 블럭은 내부적으로 모니터 락을 획득해서, 상호배제를 보장함

      ② wait() : lock을 획득해서 synchronized 블럭에 들어왔는데 만약 어떤 해당 조건에 만족하지 않아서 일을 진행할 수 없을 때, 내가 갖고 있는 lock을 풀고 wait set에 들어감

      ③ notify() : wait set에 있는 thread 중 하나를 깨워서 entry set에 넣음

      ④ notifyAll() : wait set에 있는 모든 thread를 깨워서 entry set에 넣음

   - 전체적인 흐름

      * Lock획득(synchronized 블럭 진입) -> 임계영역 실행 -> Lock반환(synchronized 블럭 탈출)

      * entry set에 있는 하나의 스레드를 빼서 synchronized 블럭에 진입

      * synchronized 블럭에 들어갔더라도, 조건이 맞지 않은 경우 lock을 풀고 wait set에 들어가고 블럭 탈출

      * 다른 스레드가 synchronized 실행 후 notify로 wait set에 있는 하나를 깨워서 entry set에 넣음


3. 예시를 보며 이해

   - Producer 1번, 2번을 P1, P2로 하고, Consumer 1,2,3번을 C1, C2, C3라 하자

   - 그림의 Buffer는 한번에 한 스레드만 사용해야 함

   - 진행

      ① C1이 Buffer(Buffer의 Lock) 획득. 하지만, Buffer가 비어있으므로(실행할 수 없는 조건) C1은 wait set에 진입

      ② P1, P2, P3가 모두 Buffer를 획득하기를 원해서 P1만 Buffer 획득. 나머지는 entry set에 진입

      ③ P1은 Buffer하나를 채운 후 notify -> wait set에 있는 하나를 깨움 -> C1은 entry set에 진입

      ④ entry set에 있는 P2, P3, C1 중 하나가 Buffer 획득

      ⑤ ... 반복

   - wait set중 어떤 것을 깨울 것인가? entry set중 어떤 것에 Lock을 줄 것인가? -> 이러한 문제는 starvation을 야기할 수 있음.

   - 또한, wait set에 Producer와 Consumer가 같이 관리되기 때문에 만약 Producer가 Buffer를 가득 채우고 wait set에 있는 다른 Producer를 깨우면, 이 Producer는 Buffer를 획득하더라도 채울 수 없기 때문에 또 wait set에 들어감. Producer가 Buffer를 채웠다면, wait set에 있는 Consumer를 깨우는게 더 효율적인 상황임


4. Java에서 Lock & Condition 방법

   - 위와 같은 문제를 해결하기 위해 Lock & Condition 방법을 제공함

   - Monitor 방법과 다른 점은 Condition에 따라 wait set을 따로 구분하는 것임. 위 문제에서는 Producer wait set과 Consumer wait set을 따로 구분함

   - 만약 P1이 Buffer를 채웠다면, Consumer wait set중 하나를 깨워서 entry set에 넣음

   - 반대로 C1이 Buffer를 소비했다면, Producer wait set중 하나를 깨워서 entry set에 넣음

   - 이렇게 스레드의 상태에 따라 종류를 나눠서 wait set을 만들 경우, 더 효율적인 상황이 됨

   - 하지만, 같은 종류의 스레드(ex. P1, P2)끼리의 경쟁은 있기 때문에 기아현상이 발생할 가능성은 여전히 있고, Entry Set중 어떤 스레드에 lock을 줄 것인지에 대한 문제도 있기 때문에 기아현상 여전히 존재

   - 공정하게 lock을 주기 위해서 java에서는 Lock lock = new ReentrantLock(boolean fair) 라는 공정성 매개변수를 제공하는데(true일 경우 오래 기다린 스레드에게 lock을 획득하게함), 그런데 그만큼 성능이 떨어지고, 대부분의 경우 공정함보다는 성능을 채택함


Java에서 Programming Level에서의 동기화 문제를 어떻게 해결하는지 알아볼 수 있었습니다.

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

시스템콜, 인터럽트  (0) 2023.09.01
동기, 비동기  (0) 2023.08.31
동기화  (0) 2023.08.11
페이지 교체 알고리즘  (0) 2023.08.10
Deadlock  (0) 2023.08.08

+ Recent posts