본문 바로가기
츄Log/기타 끄적

Double Check Locking (DCL, 이중잠금)

by 츄츄🦭 2023. 12. 1.
728x90

안녕하세요! 오늘은 스레드간 동기화 작업에서 알고 있으면 유용한 패턴인 Double Check Locking(이중잠금)에 대해서 알아보겠습니다.

 

Double Check Locking은 동기화 블록에서 불필요한 lock 획득을 줄여주는 패턴입니다.

(즉, 동기화 구문을 최소화 시켜주는 전략입니다.)

아시다시피 lock획득은 비싼 비용이 들기 때문에 불필요한 lock 획득 작업을 없애면 성능 향상을 기대해볼 수 있습니다.

 

싱글톤 패턴을 사용하는 코드를 예제로 Double Check Locking을 알아보겠습니다. 

public class Singleton {
	private static TestObject instance;
    
    public static synchronized TestObject getInstance() {
    	if (instance == null) {
        	instance = new TestObject();
        }
        
        return instance;
    }
}

지연초기화를 적용한 싱글톤 패턴 코드입니다. 

여러 스레드가 함께 접근하여 TestObject가 여러개 생길 수 있기 때문에 synchronized 키워드를 통해 동기화를 시켜주어습니다. 

덕분에 이 클래스는 thread-safe 하지만, 싱글톤 인스턴스를 얻으려고 할 때마다 synchronized 블록에 들어가게 됩니다.

처음 인스턴스를 만들 때 유효할지 몰라도 이미 인스턴스가 생긴 이후에 synchronized는 불필요해 보입니다. 

 

위 코드는 아래와 같이 개선할 수 있습니다.

public class Singleton {
	private static TestObject instance;

	public static TestObject getInstance() {
    	if (instance == null) {
        	synchronized (this) {
        		instance = new TestObject();
            }
        }
 
        return instance;
    }
}

인스턴스가 있다면 인스턴스를 리턴하고, 없으면 그 때 생성하기 위해 동기화 블록에 들어갑니다. 

 

하지만 이 코드는 컴파일 타임에 문제가 생길 수 있습니다.

컴파일러는 JMM규칙에 의해 최적화를 수행하는데, 최적화는 코드의 수행 순서를 변경 즉, reordering(재배치)하기도 합니다.

reordering으로 인해 문제가 되는 부분을 살펴보겠습니다.

 

instance = new TestObject();

이 코드에서 우리가 기대하는 것은 아래와 같습니다.

1. TestObject 인스턴스 생성

2. instance 에 할당
   

하지만 reordering으로 인해 아래와 같은 순서로도 진행될 수 있습니다.

1. instance에 할당
2. TestObject 인스턴스 생성

 

그래서 다른 스레드가 진입한 시점에 실제 인스턴스가 생기지 않았으나 instance 변수는 null이 아니므로 예상치 못한 일이 생길 수 있습니다.  (정상적이라면 다른 스레드가 진입한 시점에 실제 인스턴스가 없으므로 동기화 블록을 기다려야합니다.)

 

이 문제는 Java1.5 부터 volatile 키워드를 통해 해소할 수 있습니다.

volatile은 변수의 가시성을 보장하는 키워드로 컴파일러가 reordering을 하지 않도록 강제합니다. 

 

volatile 키워드를 적용해봅시다.

public class Singleton {
	private static volatile TestObject instance;

	public static TestObject getInstance() {
    	if (instance == null) {
        	synchronized (this) {
        		instance = new TestObject();
            }
        }
 
        return instance;
    }
}

이제 Double Check Locking을 적용하였고 volatile 키워드로 컴파일 시점에 발생할 수 있는 reordering 문제도 해결했습니다. 

 

하지만 이 방식은 두가지 단점이 있습니다. 

1. volatile 키워드를 사용할 수 있는 Java1.5부터 유효합니다.

2. 코드가 좀 장황합니다. 

이런 이유로 동기화 작업을 개발자가 하는 것이 아닌, JVM에게 위임하여 처리할 수는 없을까? 고민해 볼 수 있습니다. 

 

JVM에게 위임하여 처리하는 방법은 다음 포스팅에서 뵙겠습니다!

 

감사합니다!

궁금한 점은 댓글 남겨주세요 :) 

 

참고 : https://www.baeldung.com/java-singleton-double-checked-locking

728x90