프로그래밍 언어/Java

[Java] 뮤텍스, 세마포어와 모니터

highright96 2022. 1. 8.

Java 스터디를 진행하며 작성한 글입니다.

 

뮤텍스(Mutual Exclusion)

멀티 프로그래밍 환경에서 자원에 대한 접근에 제한을 강제하기 위한 동기화 기법이다. 특징을 살펴보면 다음과 같다.

  • Boolean 타입의 Lock 변수를 사용한다. 따라서 1개의 공유자원에 대한 접근을 제한한다.
  • 공유자원을 사용 중인 스레드가 있을 때, 다른 스레드가 공유자원에 접근한다면 Blocking 후 대기 큐로 보낸다.
  • Lock을 건 스레드만 Lock을 해제할 수 있다.

 

 

세마포어

멀티 프로그래밍 환경에서 다수의 프로세스나 스레드가 n개의 공유 자원에 대한 접근을 제한하는 방법으로 사용되는 동기화 기법이다.

  • 세마포어 변수를 통해 wait, signal을 관리한다. 세마포어 변수는 0 이상의 정수형 변수를 갖는다.
  • n 개의 공유자원에 대한 접근을 제한할 수 있으며 이를 계수 세마포어라고 한다.
  • 접근 가능한 공유 자원의 수가 1개일 때는 이진 세마포어로 뮤텍스처럼 사용할 수 있다.
  • 큐에 연결된 스레드를 깨우는 방식에 따라 강성 세마포어(큐에 연결된 스레드를 깨울 때 FIFO 정책), 약성 세마포어(큐에 연결된 스레드를 깨울 때 순서를 특별히 명시하지 않음)로 구분된다.
  • 세마포어는 Signaling 메커니즘으로 Lock을 걸지 않은 스레드도 Signal을 보내 Lock을 해제할 수 있다.

 

세마포어로 동시 접근 제어

synchronized의 경우 오직 하나의 스레드만 수행 가능하다면, 세마포어는 동시에 실행할 수 있는 스레드의 수를 제어할 수 있다.

 

동시에 수행 중인 스레드의 번호를 출력하는 예제이다.

 

public class Resource {

    private final Semaphore semaphore;
    private final int maxThread;

    public Resource(int maxThread) {
        this.maxThread = maxThread;
        this.semaphore = new Semaphore(maxThread);
    }

    public void use() {
        try {
            semaphore.acquire();
            System.out.println("[" + Thread.currentThread().getName() + "]" + (maxThread - semaphore.availablePermits())
                + "개의 스레드가 점유중");
            Thread.sleep(1000);
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

Resource 인스턴스의 use() 메소드를 정해준 maxThread 수만큼의 스레드가 동시에 호출 가능하도록 만들었다. use() 메소드를 호출하면 세마포어를 몇 개의 스레드가 점유 중인지 출력하고 1초간 sleep을 호출한다. 실제로 스레드를 생성해 동기화가 잘 지켜지는지 확인해보자.

 

public class Semaphore {

    public static void main(String[] args) {
        Resource resource = new Resource(3);
        for (int i = 1; i <= 10; i++) {
            Thread t = new Thread(resource::use);
            t.start();
        }
    }
}

 

실행 결과는 다음과 같다.

 

 

세마포어를 maxThread에 설정한 최대 스레드 수 이하로만 스레드가 점유하는 것을 확인할 수 있다. 이렇게 세마포어를 처리하는 경우 동시에 수행되는 스레드의 수를 제어하는 것이 가능하다.

 

 

모니터

뮤텍스와 세마포어는 상호 베제를 위한 동기화 개념이다. 그러나 완벽한 상호 배제를 제공한다고 할 수 없기 때문에 이를 보완까지 해둔 모니터를 사용하면 훨씬 쉽게 동기화를 사용할 수 있다.

 

세마포어는 wait & signal 연산 순서를 바꿔서 실행하거나 둘 중 하나라도 생략하면 상호 배제를 위반하는 상황이여서 교착 상태가 발생한다. 만약 wait & signal 연산이 프로그램 전체에 구성되어 있으면 세마포어의 영향이 미치는 곳이 어딘지 파악하기 어렵기 때문에 세마포어를 사용해 프로그램을 구현하는 것은 매우 어렵다. 이런 단점을 극복하기 위해 모니터가 등장했고, 이는 프로그래밍 언어 수준에서 제공된다. 대표적으로 Java가 있다.

 

모니터는 임계 구역을 지켜내기 위한 방법인 상호 배제를 프로그램으로 구현한 것이며, 이진 세마포어만 가능하다.

다음은 모니터의 구조를 간단히 나타낸 그림이다.

 

 

모니터는 공유 자원 + 공유 자원 접근함수로 이루어져 있고, 2개의 큐를 가지고 있다.

 

  • 상호베타 큐(Mutual Exclusion Queue)
    • 공유 자원에 하나의 프로세스만 진입하도록 하기 위한 큐이다. 공유 자원을 사용하는 스레드가 존재하면 상호베타 큐에 존재한다.
  • 조건동기 큐(Conditional Synchronization Queue)
    • 공유 자원의 Lock이 해제되기를 기다리는 스레드가 대기하는 큐이다.

 

모니터를 통해 프로세스가 자원에 접근하는 방식을 이미지로 보면 아래와 같다.

 

 

 

공유 자원을 점유 중인 프로세스(스레드)는 Lock을 가지고 있다. 공유 자원을 점유 중인 프로세스(스레드)가 있는 상황에서 다른 프로세스(스레드)가 공유 자원에 접근하려고 하면 외부 모니터 조건동기 큐(준비 큐)에서 진입을 기다린다.

 

synchronized

자바의 synchronized 키워드는 스레드 동기화를 할 때 사용되는 대표적인 기법이다. 자바의 모든 인스턴스는 모니터를 가지고 있으며 모니터를 통해 스레드 동기화를 수행한다(Object 내부에 존재한다).

 

모니터의 라이프 사이클은 synchronized 키워드에 의존하기 때문에, 인스턴스가 가지는 wait(), notifiy(), notifiyAll() 메소드는 모두 synchronized 블록에서만 유의미하다.

 

자세한 내용은 따로 다루도록 하겠다.

 

wait(), notify(), notifyAll()

모니터에는 조건 변수가 있는데 이를 통해 wait(), notify(), notifyAll() 메소드가 구현되어 있다.

 

  • wait()
    • Lock을 가진 스레드가 다른 스레드에 Lock을 넘겨준 다음 대기해야할 때 사용한다.
  • notifiy()
    • 대기하고 있는 스레드들 중 하나를 깨운다.
  • notifiyAll()
    • 대기하고 있는 스레드 모두를 깨운다.

자세한 내용은 따로 다루도록 하겠다.

 

 

참고

 

 

예상 면접 질문 및 답변

링크 참고

댓글