고유 락 (Intrinsic Lock)과 synchronized

한눈에 보기

고유 락은 모든 자바 객체가 내장한 모니터 락으로 synchronized가 이를 사용해 임계 영역을 보호한다. 재진입성, happens-before 가시성 보장, ReentrantLock과의 비교를 정리한다.

최종 업데이트 2026.09.17

고유 락(Intrinsic Lock)은 자바의 모든 객체가 내장하고 있는 락으로, 모니터 락(Monitor Lock)이라고도 부르며 synchronized 키워드가 이 락을 사용해 임계 영역(critical section)을 보호한다. 한 스레드가 어떤 객체의 고유 락을 쥐고 있는 동안 다른 스레드는 같은 락을 요구하는 synchronized 블록에 진입하지 못하고 대기한다. 고유 락은 상호 배제뿐 아니라 메모리 가시성(visibility)까지 보장하므로, 멀티스레드 환경에서 공유 상태를 다루는 가장 기본적인 동기화 수단이다.

synchronized의 세 가지 형태

synchronized는 어떤 객체의 락을 잡느냐에 따라 세 가지로 쓰인다. 인스턴스 메서드에 붙이면 this의 락을, static 메서드에 붙이면 해당 클래스의 Class 객체 락을, 블록으로 쓰면 지정한 객체의 락을 잡는다. 인스턴스 락과 클래스 락은 서로 다른 락이므로, 인스턴스 메서드와 static 메서드에 각각 synchronized를 붙였다고 해서 서로 배제되지는 않는다.

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

    public synchronized void increment() {   // this의 고유 락
        count++;
    }

    public static synchronized void reset() { // Counter.class의 고유 락
        // ...
    }

    public void incrementWithBlock() {
        synchronized (lock) {                 // 전용 락 객체의 고유 락
            count++;
        }
    }
}

락 대상은 좁을수록 좋다. 메서드 전체보다 꼭 필요한 구간만 블록으로 감싸는 편이 경합을 줄이며, 외부에 노출된 this 대신 private final 락 객체를 쓰면 외부 코드가 같은 락을 잡아 교착이나 성능 저하를 일으키는 것을 막을 수 있다.

바이트코드와 객체 헤더 관점

synchronized 블록은 바이트코드에서 monitorenter와 monitorexit 명령으로 컴파일되고, synchronized 메서드는 메서드 메타데이터의 ACC_SYNCHRONIZED 플래그로 표시되어 JVM이 진입과 탈출 시 락을 처리한다. 예외가 발생해도 락이 반드시 해제되도록 컴파일러가 예외 경로에도 monitorexit를 넣는다.

핫스팟 JVM에서 락 상태는 객체 헤더의 마크 워드(mark word)에 기록된다. 경합이 없으면 CAS(Compare-And-Swap) 연산만으로 획득하는 경량 락(lightweight lock)으로 동작하고, 여러 스레드가 실제로 경합하면 운영체제 동기화 자원을 쓰는 중량 락(heavyweight lock)으로 팽창(inflation)한다. 과거에 있던 편향 락(biased locking)은 유지 비용 대비 이득이 줄어 JDK 15에서 비활성화되고 이후 제거되었다. 즉 Java 21 기준 synchronized는 무경합 시 매우 저렴하고, 경합 시에만 비용이 커지는 구조다.

재진입성과 가시성

고유 락은 재진입 가능(reentrant)하다. 락을 이미 쥔 스레드가 같은 락을 요구하는 다른 synchronized 메서드를 호출해도 막히지 않고, JVM이 소유 스레드와 획득 횟수를 세어 같은 횟수만큼 해제될 때 락이 풀린다. 재진입이 안 된다면 synchronized 메서드가 같은 객체의 다른 synchronized 메서드를 호출하는 흔한 코드가 전부 자기 자신과의 교착 상태에 빠졌을 것이다.

또한 자바 메모리 모델(Java Memory Model)은 한 스레드가 락을 해제하기 전에 수행한 모든 쓰기가, 이후 같은 락을 획득한 스레드에게 보이도록 선행 발생(happens-before) 관계를 보장한다. 즉 synchronized는 원자성(atomicity)과 가시성을 함께 제공한다. 가시성만 필요하고 복합 연산의 원자성이 필요 없다면 volatile이 더 가벼운 대안이 된다.

ReentrantLock과 비교

구분synchronized (고유 락)ReentrantLock (명시적 락)
획득과 해제블록 구조에 따라 자동lock()과 unlock()을 직접 호출, finally 필수
재진입지원지원
타임아웃과 인터럽트불가능, 획득까지 무한 대기tryLock(), lockInterruptibly() 지원
공정성비공정만공정 모드 선택 가능
조건 대기wait와 notify, 대기 집합 하나Condition 여러 개 생성 가능
코드 복잡도단순유연하지만 실수 여지 있음

일반적인 상황에서는 단순하고 실수 여지가 적은 synchronized로 충분하며, 타임아웃이나 공정성, 다중 조건 큐가 필요할 때 ReentrantLock을 선택한다. 한 가지 주의할 점으로, Java 21의 가상 스레드(virtual thread)는 초기 구현에서 synchronized 블록 안에서 블로킹하면 캐리어 스레드에 고정(pinning)되는 제약이 있었고, Java 24에서 이 제약이 해소되었다. 사용하는 버전에 따라 가상 스레드와 synchronized의 조합을 점검할 필요가 있다.

면접에서 이렇게 물어본다

  • 고유 락이 무엇이고 synchronized 메서드와 블록이 각각 어떤 객체의 락을 잡는지 설명해 보라.
  • 고유 락의 재진입성이 무엇이며 왜 필요한지 예를 들어 설명해 보라.
  • synchronized가 가시성 문제를 어떻게 해결하는지 happens-before 개념으로 설명해 보라.
  • synchronized와 ReentrantLock의 차이, 그리고 각각을 언제 선택할지 말해 보라.
  • 경량 락과 중량 락으로의 팽창 등 JVM이 고유 락을 최적화하는 방식을 아는 대로 설명해 보라.
Java 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기