CS Study 8주차: [Java] 고유 락(Intrinsic lock)

hjern·2024년 4월 12일
0

CS Study

목록 보기
7/10

Intrinsic Lock (= monitor lock = monitor)

  • Java의 모든 객체는 lock을 갖고 있음.
  • 모니터처럼 동작한다고 하여 monitor lock 혹은 monitor 라고 함.
  • Synchronized 블록은 Intrinsic Lock을 이용해서, Thread의 접근을 제어함.

Thread-safe 하지 않은 연산

public class Counter {
    private int count;
    
    public int increase() {
        return ++count;		
    }
}

Q) ++count 문이 atomic 연산인가?
A) read (count 값을 읽음) > modify (count 값 수정) > write (count 값 저장)의 과정에서, 여러 Thread가 공유 자원(count)으로 접근할 수 있으므로, 동시성 문제가 발생함.

✅ 동시성 문제는 결국 여러 스레드가 공유 자원 (count 변수)에 접근하기 때문에 발생하는데 동시성 문제를 해결하기 위해 count 변수로 접근하는 스레드를 제어해야 함

  • 원자적(atomic) 연산 : 처리 중간에 다른 것이 끼어들 여지를 주지 않아 “반쯤 했다.”라는 것은 없고, 단지 “했다”와 “안했다”만 존재하는 연산이다.

Synchronized 블록을 사용한 Thread-safe Case

  • 1단계
  • lock이라는 Object의 인스턴스를 이용하여 스레드가 동시에 count 변수에 접근하지 못하도록 제어. increase() 메서드는 한 번에 한 스레드만 실행할 수 있음. 한 스레드가 먼저 락을 획득한 경우, 다른 스레드는 기다려야 함.
public class Counter {
   private Object lock = new Object();
   private int count;

   public int increase() {
     synchronized(lock) {
       return ++count;
     }
   }
 }
  • 2단계
  • Counter의 인스턴스도 자바 객체이므로 락으로 사용할 수 있어 this를 이용하여 별도의 락 생성 없이 synchronized 블록을 구현할 수 있음
public class Counter {
  private int count;

  public int increase() {
    synchronized(this) {
      return ++count;
    }
  }
}
  • 3단계
  • lock 생성 없이 synchronized 블록 구현 가능
public class Counter {
  private int count;

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

구조적인 락 Structed lock

  • 고유락을 이용한 동기화를 구조적인 락이라고 함.
  • synchronized 블록 단위로 락의 획득/ 해제가 일어나므로 구조적이라고 함.
  • 블록을 진입할 때 락의 획득이, 블록을 벗어날 때 락의 해제가 일어남.
  • 따라서, A획득 -> B획득 -> B해제 -> A해제는 가능하지만, A획득 -> B획득 -> A해제 -> B해제는 불가능함.
  • 이것을 가능하게 하기 위해서는 Reentrant Lock (명시적 Lock) 을 사용해야 함.

Reentrancy(재진입 가능성)

  • 자바의 고유락은 재진입이 가능
  • 재진입(Lock을 획득한 Thread가 같은 Lock을 얻기 위해 대기할 필요가 없음)이 가능하다는 것은 락의 획득이 호출 단위가 아닌 스레드 단위로 일어난다는 것을 의미
  • 이미 락을 획득한 스레드는 같은 락을 얻기 위해 대기할 필요가 없음 > synchronized 블록을 만났을 때, 대기없이 통과 가능
public class Reentrancy {
    // b가 Synchronized로 선언되어 있더라도, a 진입시 lock을 획득하였음.
    // b를 호출할 수 있게 됨.
    public synchronized void a() {
        System.out.println("a");
        b();
    }
    
    public synchronized void b() {
        System.out.println("b");
    }
    
    public static void main (String[] args) {
        new Reentrancy().a();
    }
}

Visibility

  • 가시성 : 여러 Thread가 동시에 작동하였을 때, 한 Thread가 쓴 값을 다른 Thread가 볼 수 있는지, 없는지 여부.
  • 문제 : 하나의 Thread가 쓴 값을 다른 Thread가 볼 수 있느냐 없느냐. (볼 수 없으면 문제가 됨).
  • 발생 이유 : 최적화를 위해 컴파일러나 CPU에서 발생하는 코드 재배열이나 멀티 코어 환경에서 코어의 캐시 값이 메모리에 제때 쓰이지 않아 문제가 발생할 수 있음
  • Structure Lock과 Reentrant Lock은 visibility 보장

예상 질문

Q. 고유락(Intrinsic Lock)이란 무엇인가요?
✅ 고유락은 모든 자바 객체가 가지며, synchronized 블록과 고유락을 이용하여 스레드의 접근을 제어할 수 있습니다.

Q. Synchronized 블록을 어떻게 사용하여 Thread-safe를 구현할 수 있나요?
✅ lock이라는 Object의 인스턴스를 이용하여 스레드가 동시에 count 변수에 접근하지 못하도록 제어하거나 Counter 의 인스턴스를 이용해 this 로 제어하거나 메서드에 synchronized 키워드를 붙여주는 것으로 구현할 수 있습니다.

Q. 구조적인 락(Structured Lock)은 무엇인가요?
✅ synchronized 블록 단위로 락의 획득과 해제가 이루어지는 방식을 의미하며 블록에 진입할 때 락을 획득하고, 블록을 벗어날 때 락을 해제합니다. 이로 인해 특정 순서로 락을 획득하고 해제하는 제약이 발생하는데, 가령 A락 획득 > B락 획득 > B락 해제 > A락 해제 순서와 같은 제약이 생깁니다.

Q. Reentrancy(재진입 가능성)란 무엇인가요?
✅ Java의 고유락은 재진입이 가능하며, 이미 락을 획득한 스레드는 동일한 락을 다시 획득할 때 대기할 필요 없이 바로 진행됩니다.

Q. Visibility 문제는 무엇인가요?
✅ Visibility는 여러 스레드가 동시에 메모리의 값을 제대로 볼 수 있는지의 문제이며, 최적화나 멀티 코어 환경에서 발생하는 코드 재배열, 코어 캐시의 문제 등으로 발생할 수 있습니다. synchronized 또는, Reentrant Lock을 사용하면 visibility 문제를 해결할 수 있습니다.

참고자료
Java의 고유 락(intrinsic lock)에 대해
<CS 지식> Java 고유락 (Intrinsic Lock)
원자적(atomic) 연산과 순서(ordering) 제약

profile
주니어의 굴레는 언제 벗어날 것인가

0개의 댓글