[CS] 13) Java의 고유락(intrinsic lock)

songh·2024년 2월 26일
0

CS지식

목록 보기
14/35
post-thumbnail

고유락과 synchronized 블록

자바의 모든 객체는 락을 갖고 있다. 모든 객체가 가지고 있으니 고유락(intrinsic lock)이라고도 하고 모니터처럼 동작한다고 해서 모니터락 혹은 모니터라고도 한다. 자바의 synchronized블록은 동시성문제를 해결하는 가장 간편한 방법으로 고유락을 이용해 여러 스레드의 접근을 제어한다.

public class Counter {
  private int count;

  public int increase() {
    return ++count; // 스레드 안전하지 않은 연산
  }
}

++연산자를 호출할때 세가지 연산이 순차적으로 연산된다. 변수를 메모리에서 읽고 증가시키고 다시 메모리에 쓰는 과정이 있다. 이는 동시성프로그래밍에서 문제가 되는 전형적인 read-modify-write 패턴이다. 두 스레드가 동시에 같은 값을 읽고 값을 증가시켜서 저장하면 count 값에 차이가 발생한다. 예를 들어 두 스레드가 한번씩 increase()를 호출할때 각각 0에서 1로 증가시키므로 최종적으로 count = 1이 찍히게 되는데, 이를 동시성 문제라고 한다.

이러한 동시성 문제는 결국 여러 스레드가 공유자원으로 접근하기 때문에 발생한다. 여기서 공유자원은 count변수로 동시성문제를 해결하기 위해 count변수로 접근하는 스레드를 제어해야 한다. 고유락 즉 synchronized 블록을 이용해 count 클래스로 접근하는 스레드들의 동시성문제를 해결해본다.

1. Object 인스턴스로 제어하는 방법

public class Counter {
  private Object lock = new Object();
  private int count;

  public int increase() {
    synchronized(lock) {
      return ++count;
    }
  }
}

lock이라고 하는 Object인스턴스를 생성해 여러 스레드가 동시에 count변수에 접근하지 못하도록 제어했다. increase()메서드는 한번에 하나의 스레드에서만 실행할 수 있다. 한 스레드가 먼저 락을 획득한 경우 다른 스레드는 기다려야한다.

2. Object 인스턴스 없이 제어하는 방법

public class Counter {
  private int count;

  public int increase() {
    synchronized(this) { //바뀐 부분
      return ++count;
    }
  }
}

그리고 락을 위해 별도 객체를 생성할 필요는 없다. Count의 인스턴스도 자바객체이므로 락을 사용할 수 있다.

3. synchronized 키워드 사용하는 방법

public class Counter {
  private int count;

  public synchronized int increase() { //바뀐부분
      return ++count;
  }
}

재진입가능성(Reetrancy)

자바의 고유락은 재진입이 가능하다. 재진입이 가능하다는 것은 락의 획득이 호출단위가 아니라 스레드단위로 일어난다는 것을 의미하다. 이미 락을 획득한 스레드는 같은 락을 얻기 위해 대기할 필요가 없고 이미 락을 가지고 있으므로, 락에 대한 synchronized 블록을 만났을때 대기없이 통과할 수 있다.

public class Reentrancy {
  public synchronized void a() {
    System.out.println("a");
    // b가 synchronized로 선언되어 있지만 a진입시 이미 락을 획득하였으므로,
    // b를 호출할 수 있다.
    b();
  }

  public synchronized void b() {
    System.out.println("b");
  }

  public static void main(String[] args) {
    new Reentrancy().a();
  }
}

구조적인 락

고유락을 이용한 동기화를 구조적인 락이라고 한다. synchronized블록 단위로 락의 획득/해제가 일어나므로 구조적이라고 한다. 블록 진입할때 락의 획득이 일어나고 블록을 벗어날때 락의 해제가 일어난다. 따라서 구조적인 락 A 와 B가 있을때 A획득-B획득-B해제-A해제가 가능하다.

가시성

동시성 프로그램의 이슈 중 하나는 가시성이다. synchronizd 적용 전 Count예제에서 두 스레드가 동시에 increase()를 호출하는 일이 없다고 해도 문제가 있다. 한 스레드가 쓴 값을 다른 스레드가 볼수도 있고 그렇지 않을 수 있기 때문이다. 이를 가시성문제라고 하는데 이 문제의 원인은 다양하다. 최적화를 위해 컴파일이나 CPU에서 발생하는 코드 재배열때문에 이런 문제가 발생할 수도 있고 멀티코어 환경에서 코어의 캐시값이 메모리에 제때 쓰이지 않아 발생할 수 있다.

락을 사용하면 가시성 문제가 해결된다. 자바에서 스레드가 락을 획득하는 경우, 이전에 쓰였던 값들의 가시성을 보장한다. 즉, synchronized가 적용된 예제에서 스레드 A가 쓴 값을 스레드 B가 읽을 수 있다. 이는 명시적인 락에서도 똑같이 적용된다.

0개의 댓글

관련 채용 정보