Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

자바(Java) 객체 수준 락과 클래스 수준 락의 차이 완벽 정리

멀티스레드 환경에서 객체 수준 락(Object Level Lock)클래스 수준 락(Class Level Lock)은 모두 동기화(Synchronization)를 구현하기 위해 사용되는 핵심 메커니즘입니다. 두 락의 개념과 동작 방식을 예제 코드와 함께 자세히 살펴보겠습니다.

객체 수준 락(Object Level Lock)이란?

자바에서 모든 객체는 자신만의 고유한 락(unique lock)을 가지고 있습니다. 어떤 스레드가 특정 객체에 대해 synchronized 메서드를 실행하려면, 먼저 해당 객체의 락을 획득해야 합니다. 락을 획득한 스레드는 그 객체의 어떤 synchronized 메서드든 실행할 수 있으며, 메서드 실행이 끝나면 스레드가 자동으로 락을 해제합니다. 이러한 락의 획득과 해제 과정은 내부적으로 JVM이 관리하므로 개발자가 직접 처리할 필요가 없습니다.

객체 수준 락은 비정적(non-static) 메서드비정적 코드 블록을 동기화하고 싶을 때 사용하는 방식입니다. 이렇게 하면 클래스의 특정 인스턴스에 대해 한 번에 하나의 스레드만 해당 코드 블록을 실행할 수 있으며, 인스턴스 단위 데이터의 스레드 안전성(thread-safety)을 보장할 수 있습니다.

객체 수준 락 예제

public class ObjectLevelLockTest implements Runnable {
    @Override
    public void run() {
        objectLock();
    }
    public void objectLock() {
        System.out.println(Thread.currentThread().getName());
        synchronized(this) {
            System.out.println("Synchronized block " + Thread.currentThread().getName());
            System.out.println("Synchronized block " + Thread.currentThread().getName() + " end");
        }
    }
    public static void main(String[] args) {
        ObjectLevelLockTest test1 = new ObjectLevelLockTest();
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test1);
        ObjectLevelLockTest test2 = new ObjectLevelLockTest();
        Thread t3 = new Thread(test2);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

실행 결과

t1
t2
t3
Synchronized block t1
Synchronized block t3
Synchronized block t1 end
Synchronized block t3 end
Synchronized block t2
Synchronized block t2 end

위 결과에서 주목할 점은 t1t2가 동일한 객체(test1)를 공유하기 때문에 synchronized 블록에 동시에 진입하지 못한다는 것입니다. 반면 t3는 별도의 객체(test2)를 사용하므로 다른 스레드와 독립적으로 블록을 실행할 수 있습니다.

클래스 수준 락(Class Level Lock)이란?

자바에서 모든 클래스 역시 하나의 고유한 락을 가지며, 이것이 바로 클래스 수준 락입니다. 스레드가 static synchronized 메서드를 실행하려면 클래스 수준 락을 획득해야 합니다. 클래스 수준 락을 얻은 스레드는 해당 클래스의 어떤 static synchronized 메서드든 실행할 수 있고, 메서드 실행이 완료되면 자동으로 락이 해제됩니다.

클래스 수준 락의 가장 큰 특징은, 런타임에 존재하는 모든 인스턴스 중 어느 것을 통해서라도 여러 스레드가 synchronized 블록에 동시에 진입하는 것을 막아준다는 점입니다. 즉, 객체마다 락이 따로 존재하는 것이 아니라 클래스 전체에 단 하나의 락이 적용됩니다.

클래스 수준 락 예제

public class ClassLevelLockTest implements Runnable {
    @Override
    public void run() {
        classLock();
    }
    public void classLock() {
        System.out.println(Thread.currentThread().getName());
        synchronized(ClassLevelLockTest.class) {
            System.out.println("Synchronized block " + Thread.currentThread().getName());
            System.out.println("Synchronized block " + Thread.currentThread().getName() + " end");
        }
    }
    public static void main(String[] args) {
        ClassLevelLockTest test1 = new ClassLevelLockTest();
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test1);
        ClassLevelLockTest test2 = new ClassLevelLockTest();
        Thread t3 = new Thread(test2);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

실행 결과

t3
t2
t1
Synchronized block t3
Synchronized block t3 end
Synchronized block t2
Synchronized block t2 end
Synchronized block t1
Synchronized block t1 end

객체 수준 락 예제와 달리, 위 결과에서는 세 스레드(t1, t2, t3)가 서로 다른 객체를 사용하더라도 synchronized 블록에 한 번에 하나씩만 진입하는 것을 확인할 수 있습니다. 이는 synchronized(ClassLevelLockTest.class)처럼 클래스 객체를 기준으로 락을 걸었기 때문입니다.

두 락의 핵심 차이 요약

  • 객체 수준 락: 인스턴스별로 존재하며, synchronized(this) 또는 일반 synchronized 메서드로 획득합니다. 같은 인스턴스를 공유하는 스레드 간에만 상호 배제가 적용됩니다.
  • 클래스 수준 락: 클래스당 하나만 존재하며, synchronized(ClassName.class) 또는 static synchronized 메서드로 획득합니다. 모든 인스턴스에 걸쳐 상호 배제가 적용됩니다.

따라서 인스턴스 상태를 보호할 때는 객체 수준 락을, static 필드처럼 클래스 전체가 공유하는 자원을 보호할 때는 클래스 수준 락을 사용하는 것이 올바른 설계 방식입니다.