Java와 쓰레드 모니터

최근 Java의 동기화 방법에 대해 알아보다, 모니터라는 용어가 등장했습니다.

동기화 기법으로 뮤텍스나 세마포어에 대해 알고 있긴 했어도, 모니터는 다소 생소했습니다.

이번 시간에는 이 모니터가 무엇이고, Java를 통해 구체적인 개념을 알아보도록 하겠습니다!



모니터 등장배경

모니터는 뮤텍스와 세마포어의 문제점을 해결하기 위해 등장했습니다.

완벽해보이는 뮤텍스와 세마포어에 어떤 문제가 있을까요?

그 문제는 바로 사람으로부터 발생하는 오류입니다.

아래 경우를 볼까요?

//-----[정상적인 뮤텍스 구현]-----
wait(mutex)

/*Critical Section*/

signal(mutex)

//-----[뮤텍스 구현 실수 : wait, signal 순서 바뀜]-----
signal(mutex)

/*Critical Section*/

wait(mutex)

//-----[뮤텍스 구현 실수 : signal 존재X]-----
wait(mutex)

/*Critical Section*/

wait(mutex)

우리가 개발팀의 팀장이라고 해봅시다.

새로 들어온 신입 개발자가 위와 같은 실수를 전혀안할까요?

사람이기 때문에, 실수할 가능성이 있습니다. (굳이 신입이 아니더라도, 실수는 누구나 할 수 있습니다.)

이런 문제를 해결하기 위해, 모니터가 등장했습니다!



모니터란?

다시 말하자면, 모니터는 뮤텍스와 세마포어를 구현함에 있어서 발생할 수 있는 인적 오류를 해결하기 위해 등장했습니다.

그리고 모니터는 프로그래밍 언어 수준에서 제공되며, 가장 대표적인 예시로 Java가 있습니다.

Critical Section을 지켜내기 위한 방법인 상호 배제(Mutual Exclusion)를 프로그램으로 구현한 것입니다.

그리고 세마포어와 유사하게 시그널링 메커니즘(wait, notify 등)을 사용해서 동작합니다.

특정 쓰레드가 어떤 객체의 모니터를 얻는다는 것은, 곧 해당 객체에 락을 건다는 의미입니다.


모니터의 구성요소

모니터는 주요 구성요소는 공유자원 , 공유자원에 대한 작업 (함수) , Entry Queue , Waiting Queue , Condition Variable 입니다.

  • Condition Variable (조건변수)
    • 쓰레드 간의 시그널을 주고 받는 방법을 제공합니다.
    • 이를 통해서, wait()notify() 같은 함수를 호출해 쓰레드를 조작할 수 있습니다.
  • Entry Queue
    • 특정 객체의 임계영역에 접근할 때, 쓰레드는 해당 객체의 모니터(락)을 얻어야 합니다.
      만약 모니터(락)을 얻을 수 없다면 진입큐 Entry Queue 에 들어가서 대기합니다.
  • Waiting Queue
    • 특정 객체의 임계영역에서 작업 중인 쓰레드가 wait() 를 호출하면, 해당 쓰레드가 Wait 상태로 바뀌며 Waiting Queue 에서 대기하게 됩니다.
    • 만약 notify() 가 호출되면 대기 중인 특정 쓰레드를 Entry Queue 로 옮깁니다.



Java로 알아보는 모니터

그럼 모니터에 대해 구체적으로 알아볼까요?

Java는 이 모니터 구조를 사용해서 동기화하기 때문에, Java를 통해 알아보면 도움이 될 것 같습니다.


Java에서의 모니터

Java의 모든 객체에는 각각의 모니터가 적용되어 있습니다.
(모든 객체는 Heap 영역에 존재하고, Heap 영역에서 쓰레드 간 공유가 이루어지기 때문입니다.)

그렇기 때문에, 모든 객체에 대해서 상호 배제를 구현할 수 있습니다.

//-----[직접 뮤텍스·세마포어 구현시]-----
wait(mutex)
/* Critical Section */
signal(mutex)

//-----[모니터 사용시]-----
synchronized (target) {
	/* Critical Secion */
}

위 예시 코드를 통해, 프로그래밍 언어에서 동기화 기법을 제공하는게 얼마나 간편하고, 실수를 줄일 수 있는지 느껴지실겁니다!

Untitled


synchronized 키워드

synchronized 는 임계영역에 해당하는 코드 블록이나 메서드를 선언할 때 사용하는 자바 키워드입니다.

방금 위에서, Java의 모든 객체에 각각 모니터가 적용되어있다고 설명드렸습니다!

따라서 synchronized 키워드를 통해, 원하는 로직을 Critical Section으로 설정할 수 있습니다.

이때, 락을 가져올 객체를 지정해야 하는데 그 방법은 아래와 같습니다.

//코드 블록 : 해당 object의 락을 가져온 뒤, 코드 블록을 실행합니다.
synchronized (object) {

}

//메서드 : 자기자신 객체(this)의 락을 가져온 뒤, 메서드를 실행합니다.
public synchronized void myMethod()


wait() , notify() , notifyAll() 메서드

  • wait() : 현재 모니터를 얻은 쓰레드를 대기상태로 전환합니다. 모니터를 release하고 Waiting Queue에 들어갑니다.
  • notify() : 현재 모니터 Condition Variable의 Waiting Queue에서 대기중인 쓰레드 하나를 깨워서, Entry Queue 로 옮깁니다.
  • notifyAll() : 현재 모니터 Condition Variable의 Waiting Queue에서 대기중인 쓰레드 모두를 깨워서, Entry Queue 로 옮깁니다.

wait() , notify() , notifyAll() 메서드는 현재 임계영역을 실행 중인 쓰레드에서만 실행 가능합니다.

왜냐하면 이 메서드들은 모니터에 종속되어있기 때문인데요.

위에서 Condition Variable을 통해서 해당 메서드들을 실행할 수 있다고 설명했습니다.
그리고 Condition Variable은 모니터의 구성요소 중 하나이기 때문에, 반드시 모니터를 획득한 쓰레드에서 실행되어야 합니다.



정리하며…

지금까지 모니터의 개념과 동작원리에 대해 알아봤습니다.

덕분에 관련 개념에 대해서 명확히 이해한 것 같습니다.

자료를 정리하던 중, 각각의 용어가 조금씩 다르고 설명하는 내용도 달라 약간 혼란스러웠지만 최대한 명확히 정리해봤습니다.

만약 글에 오류가 있다면 지적해주세요! 바로 수정하겠습니다.

읽어주셔서 감사합니다.