2024-01-19
1. 예제
아래와 같이 싱글톤 패턴이 있다고 할 때 몇 가지 문제점이 발생할 수 있다.
class SampleSingleton {
private static SampleSingleton instance;
public static synchronized SampleSingleton getInstance() {
if (instance == null) {
instance = new SampleSingleton();
}
return instance;
}
}
2. volatile
private static SampleSingleton instance;
위의 변수는 volatile 로 선언되어 있지 않아 멀티스레드의 환경에서의 안정성이 떨어질 수 있다. 멀티 스레드 환경에서는 각 스레드가 자체 cpu 캐시를 가질 수 있는데, 이로 인해 한 스레드에서 변수를 수정하더라도 다른 스레드에 즉시 반영되지 않을 수 있게 된다.
위와 같은 문제를 volatile 을 통해 해결할 수 있다. volatile 은 가시성(Visibility) 과 순서(Ordering)와 연관이 있는 키워드로 써
가시성(Visibility) 은 쓰레드스레드 간에 변수의 변경 사항이 즉시 반영되도록 보장한다. 즉, 한 스레드에서 변수를 수정하면, 다른 스레드에서 해당 변수를 읽을 때 최신의 값을 확인할 수 있다.
순서(Ordering) 는 변수의 읽기와 쓰기가 일정한 순서를 갖도록 보장한다. 다시 말해, 변수에 대한 모든 쓰기가 변수에 대한 모든 읽기보다 먼저 수행된다.
위 두가지 속성을 통해 해당 인스턴스 변수를 아래와 같이 수정하면 멀티스레드 환경에서도 해당 변수는 항상 최신데이터를 가지고 있을 수 있도록 만들 수 있다.
private static volatile SampleSingleton instance;
3. 생성자
외부에서 접근할 수 없도록 생성자를 private로 명시적으로 만들어 둘 필요가 있다. 아래의 코드를 추가해 준다.
private SampleSingleton() {
// private 생성자로 외부에서의 인스턴스 생성을 막음
}
4. 이중 체크 락(Double-Checked Locking)
이전코드는 매번 인스턴스를 가져올 때마다 동기화를 획득했지만 개선된 코드에서는 객체를 생성해야 되는지 여부를 먼저 확인한 후 없을 경우에만 잠금을 걸어 속도향상을 기대할 수 있다.
public static SampleSingleton getInstance() {
if (instance == null) {
synchronized (SampleSingleton.class) {
if (instance == null) {
instance = new SampleSingleton();
}
}
}
return instance;
}
5. 전체코드
public class SampleSingleton {
private static volatile SampleSingleton instance;
private SampleSingleton() {
// private 생성자로 외부에서의 인스턴스 생성을 막음
}
public static SampleSingleton getInstance() {
if (instance == null) {
synchronized (SampleSingleton.class) {
if (instance == null) {
instance = new SampleSingleton();
}
}
}
return instance;
}
}
6. 추가적인 문제점
하지만 위의 코드들도 추가적인 문제점이 존재한다.
- volatile 키워드가 필요하여 자바 1.4 이하 버전에서는 사용할 수 없다.
- 쓸데없이 코드가 길어 읽기 힘들어진다.
7. 다른 방법
Early Initialization
스레드 안전성을 달성하는 가장 쉬운 방법은 객체 생성을 인라인으로 설정하거나 동등한 static 메서드를 사용하는 것이다. 이는 static 필드와 메서드가 차례로 초기화된다는 사실을 활용한다.
public class SampleSingleton {
private static final SampleSingleton INSTANCE = new SampleSingleton();
public static SampleSingleton getInstance() {
return INSTANCE;
}
// private constructor and other methods...
}
Initialization on Demand
Java는 메서드나 필드 중 하나를 처음 사용할 때 클래스 초기화가 발생하기 때문에 아래와 같이 중첩된 static 클래스를 사용하여 지연 초기화를 구현할 수 도 있다.
public class InitOnDemandSingleton {
private static class InstanceHolder {
private static final InitOnDemandSingleton INSTANCE = new InitOnDemandSingleton();
}
public static InitOnDemandSingleton getInstance() {
return InstanceHolder.INSTANCE;
}
// private constructor and other methods...
}
8. 출처
https://www.baeldung.com/java-singleton-double-checked-locking
메인 이미지 출처 : 사진: Unsplash의William Warby