OS
[ 동기화 #5 ] 모니터 in JAVA
qpmi1zm29
2023. 12. 27. 19:22
- 자바에서 모든 인스턴스는 1개의 모니터를 가지고 있으며, 각 모니터는 1개의 ConditionVariable만을 가질 수 있다.
- synchronized 키워드를 통해 동기화 메커니즘을 코드적으로 구현할 수 있다.
- 인스턴스 lock 뿐만 아니라 클래스 lock 또한 존재한다.
- 인스턴스 lock 과 클래스 lock은 별개의 lock으로 공유되지 않는다.
- 자바는 기본적으로 signal and continue 방식을 지원한다.
자바에서 모니터 연산 ?
앞서 보았던 모니터 연산에서 메소드 명만 변경이 되었고 역할은 동일하다.
wait () : 스레드가 lock을 취득했으나, 코드 실행 조건에 맞지 않아 해당 스레드를 condition variable의 waiting queue를 통해 wait 상태로 전환한다.
notify() : 모니터에 존재하는 단일 condition variable의 waiting queue에 존재하는 스레드 중 하나를 깨운다.
nofityAll(): 모니터에 존재하는 단일 condition variable의 waiting queue에 존재하는 모든 스레드를 깨운다.
실제 어플리케이션 개발 시, 직접 wait이나 notify를 호출하는 것은 매우 위험하다.
public class Monitor {
private int[] buffer = new int[5];
private int index = 0;
public synchronized void producer() {
int item = index * 10 ;
while ( index == 4 ) {
wait();
}
buffer[index++] = item;
notifyAll(); // producer와 consumer 모두 같은 CV를 사용해야 해서 모든 스레드를 깨워야 한다.
}
public void consumer() {
int i = -1;
synchronized ( this ) { // synchronized 블럭의 인자로 this를 사용해야 synchronized 메소드와 같은 lock을 공유할 수 있다.
while ( index == 0 ) {
wait();
}
i = buffer[--index];
notifyAll();
}
}
}
lock을 획득한 후 변경한 변수 값에 대해서 다음 lock을 획득한 대상에게 가시성을 보장하기 때문에, index 값을 조작하였을 경우 로직이 정상 동작 하게 된다.
위 코드 예시는 모두 인스턴스 lock을 사용한 예시인데, 클래스 lock을 사용하는 경우도 존재한다.
클래스 lock 사용 예시 1
public static synchronized void producer() {
int item = index * 10 ;
while ( index == 4 ) {
wait();
}
buffer[index++] = item;
notifyAll();
}
synchronized 메소드에 static을 붙이면 클래스 lock을 생성하게 된다.
물론 static 클래스 변수를 다루는 것이 아니라면 인스턴스 lock을 주로 사용하지 않을까 싶다.
public static void main ( String[] args ) {
Monitor monitor1 = new Monitor();
Monitor monitor2 = new Monitor();
Runnable r1 = () -> {
monitor1.producer();
};
Runnable r2 = () -> {
monitor2.producer();
};
r1.run();
r2.run();
}
서로 다른 Monitor 인스턴스의 메소드를 실행시키지만, r1.run() 과 r2.run()는 producer()를 호출할 시, 동기화가 발생하게 된다.
클래스 lock 사용 예시 2
public void consumer() {
int i = -1;
B b = new B();
synchronized ( Monitor.class ) {
while ( index == 0 ) {
wait();
}
i = buffer[--index];
notifyAll();
}
}
첫번째 사용 예시와 같이 클래스 lock을 사용하여 서로 다른 인스턴스 이더라도 동기화가 발생하게 되는 코드이다.
여기서 만약에 synchronized 블록 인자로 b 인스턴스를 넣게되면 어떨까??
public void consumer() {
int i = -1;
B b = new B();
synchronized ( b ) {
while ( index == 0 ) {
wait();
}
i = buffer[--index];
notifyAll();
}
}
이 경우 인스턴스 lock을 사용하는 예시인데, 메소드 호출 시 마다 매번 다른 B의 인스턴스를 사용하기 때문에, 하나의 Monitor 인스턴스에서 consumer() 를 여러 번 호출하여도 lock을 공유하지 않기 때문에 동기화가 되지 않는다.
클래스 lock은 private 생성자와 static 메소드를 사용하여 싱글톤을 구현하는 예시에서도 사용된다.