본문 바로가기

 

1. Monitor

 

  Monitor는 동시성 제어를 위한 기법이다. 세마포어나 락보다는 고수준의 기법으로 프로그래밍 언어수준에서 지원한다. 세마포어의 경우는 상황에따라 timing error가 발생할 수 있는데 이는 찾기도 힘들고 어떤경우에는 생길수도 생기지 않을수도 있다.

 

  타이밍 에러에 대해서 간단하게 설명해보면 임계구역에 접근하기 전에 wait를 실행해야하고 임계구역을 나올때 siginal을 실행해야하는데 만약 순서를 잘못 지키면 타이밍 에러가 발생한다. wait후에 signal을 호출해야하는데 또다시 wait를 작성하면 교착상태가 발생할 수 있다.

 

  이를 해결하기 위해서 언어차원에서 Monitor라는 기법을 제공하는 것이다. Monitor는 결국 세마포어를 개발자가 직접 관리할때 구현을 잘못할 수 있는데 이를 방지하기 위해서 siginal과 wait를 대신 관리해주는 솔루션이다.  모니터와 결합된 데이터가 동시에 두개 이상의 스레드에 의해 접근할 수 없도록 막는 잠금기능을 제공해서 동기화를 수행한다. 자바에서는 기본적으로 synchronized 블럭에 의해 동기화되는 모든 객체에 고유한 모니터가 결합되어 동기화를 수행한다. 만약, synchronized로 선언된 코드를 실행할때는 각 스레드는 모니터락을 획득해야 해당 작업을 수행할 수 있다. 모니터에서는 락을 획득한 스레드 정보와 Lock Count정보를 저장해두고 있다.

 

 

1.1 Monitor구성 및 기본원리

 

 

  Monitor는 공유데이터, 공유 데이터를 다루는 연산들, 초기화 코드로 구성되어 있다. 한번에 하나의 프로세스만 모니터 내부의 프로시저를 사용할 수 있고 모니터에 접근하지 못하는 프로세스는 entry queue라는 곳에서 대기하고 있는다. 이를 Mutual Exclusion하다고 할 수 있는데 두개 이상의 스레드가 critical section에 진입하지 못하도록 하는것이다. 또한 모니터가 접근순서도 제어해주고 있는데 이를Cooperation이라고 한다.

 

  먼저 스레드가 동기화 코드영역에 접근하기 위해서 Entry Set이라는 공간에 진입한다. (Runnable -> Blocked상태)  그리고 모너티락을 소유할 수 없는 상태라면 모니터락을 현재 사용중인 스레드가 반납할때까지 Entry Set에서 대기한다. 어느 시점에 모니터락을 획득가능하다면 EntrySet에서 대기중인 스레드중 하나가 모니터락을 획득하고 작업을 수행한다(Blocked -> Runnable). 마지막을 동기화 코드 영역을 떠나면 모니터락을 반환한다.

 

  위의 과정중에서 스레드가 모니터락을 획득하였더라도 작업중에 조건이 만족하지 않아서 대기를 해야한다면 스레드는 모니터락을 반납하고 Wait Set이라는 공간에 들어가서 대기한다. (Runnable -> Waiting or Timed Waiting) 그리고 다른 스레드가 진입해서 어떤 작업을 수행해주고 Wait Set이라는 공간에서 대기중인 스레드에게 신호를 보낸다(notify,notifyAll). 최종적으로 Wait Set에서 다시 Entry Set으로 이동해서 monitor획득을 위해서 기다린다. (Waiting or Timed Waiting -> Blocked)

 

 

1.2 Synchronized

 

1.2.1 synchronized keyword

  • 임계영역에 해당하는 코드 블록을 선언할때 사용하는 자바키워드
  • 임계영역으로 설정되고 해당 영역에 다수의 스레드가 접근한다면 모니터락을 획득해야 진입이 가능하다.
  • synchronized keyword가 있는 객체는 모니터락을 가진 객체 인스턴스가 생성된다.
//critical section
public synchronized void add() {
 
}

1.2.2 wait,notify

  • java.lang.Object클래스에 선언되어 있는 메서드이다.
  • 스레드가 어떤 객체의 wait를 호출하면 해당 객체의 모니터락을 획득하기 위해서 wait set으로 진입한다.
  • 스레드가 어떤 객체의 notify를 호출하면 wait set에서 대기중인 스레드를 깨워서 다시 entry set에 넣어서 lock 획득을 기다리도록 한다.
  • notifyAll을 호출하면 wait set에 있는 모든 스레드를 깨워서 entry set에 넣어서 lock획득을 기다리도록 한다.

 

 

2. Volatile

 

  volatile은 멀티 스레드 환경에서 동기화를 시켜주는 키워드이다. volatile키워드를 선언하면 해당변수의 값을 읽을때 CPU캐시가 아니라 컴퓨터의 메인 메모리로 부터 읽는다. volatile은 모든 경우의 상황을 처리해주지는 못하고 atomic 연산에서만 동기화를 보장해주는 특징이 있다. 즉, `volatile은 하나의 스레드만 write하고 나머지 스레드들은 read만 하는 상황에서만 항상 동기화된 값`을 얻을 수 있다.

 

※ atomic 연산 

  • 모든 조작을 완료할 때까지 어떤 프로세스도 변경을 알지 못하도록 비가시적이어야 한다
  • 조작중 어떤 문제가 생긴다면 전체 조작이 실패하고 원래 상태로 복구되어야 한다.

 

예를들어, 변수 a=1을 선언하고 1번쓰레드가 a값을 증가하기 위해서 값을 읽고 1+1하고 2로 계산을 하고 2번 스레드가 a값을 증가시키기 위해서 a값을 얻는데 이때 a는 1이다. 그리고 나서 1번스레드가 2라는 값을 저장하고 2번스레드 또한 다시 2를 a변수에 넣으므로 예상되는 값 3이 아니라 2가 되는 문제가 있다.

 

아래 연산에서는 n+1을 하는 과정에서 다른 스레드에서 n변수를 읽어간다면 atomic하지 않기때문에 동기화를 보장하지 못한다. 이를 해결하기 위해서는 결국 synchronized를 통해서 작업 자체를 atomic하게 만들거나 연산을 atomic하도록 수정해야한다.

 volatile int n = 1;
 public void modify() {
    int temp = n + 1;
    n = temp;
 }

 

 

이러한 이유때문에 java에서 atomic연산을 지원하기 위한 구현체중 하나인 AtomicInteger에서는 volatile을 선언한 value라는 변수가 있는것을 확인할 수 있다.

public class AtomicInteger extends Number implements java.io.Serializable {

    private static final long serialVersionUID = 6214790243416807050L;

    /*
     * This class intended to be implemented using VarHandles, but there
     * are unresolved cyclic startup dependencies.
     */
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

    private volatile int value;

    // ...

    /**
     * Atomically sets the value to {@code newValue}
     * if the current value {@code == expectedValue},
     * with memory effects as specified by {@link VarHandle#compareAndSet}.
     *
     * @param expectedValue the expected value
     * @param newValue the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expectedValue, int newValue) {
        return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
    }
    
    // ...
}

 

 

'Java' 카테고리의 다른 글

자바 객체 생성되는 과정에 대해서  (0) 2021.09.02
Java 배열에 대해서  (0) 2021.08.27
자바 String 클래스에 대해서  (1) 2021.08.27
Thread Dump로 DeadLock 확인하기  (0) 2021.06.12
자바 - enum  (0) 2020.09.01

댓글