책을 보다가 싱글톤 패턴에 대한 코드를 봤는데,
내가 사용하는 코드와 다른 방식으로 설명을 해서
챗봇에게 책에서 본 코드와 내가 사용하는 코드를 비교해서 설명해달라고 부탁했다.
결론부터 얘기해보자면
두개는 다른 패턴의 싱글톤 구현 코드였고,
내가 사용하던 코드에는 심지어 문제가 있었다. 🙊
관련해서 두 가지 문제점을 지적받았는데,
synchronized 블록을 사용하지 않았기 때문에
여러 스레드가 동시에 getInstance()에 접근하게 된다면 Hello 인스턴스가 여러번 생성될 수 있는 문제가 있었고,
멀티스레드 환경에서
Hello 인스턴스를 생성한 후 다른 스레드가 이것을 볼 수 없는 경우가 발생할 수 있다고 한다.
인스턴스 생성 과정에서 컴파일러 최적화와 CPU 캐시 메모리의 동기화 문제 때문이었다.
이 문제를 해결하려면 volatile 키워드를 사용해서 INSTANCE 변수를 생성하면 된다.
volatile 키워드는 변수가 항상 메인 메모리에서 읽고 쓰여야 함을 나타내고,
이를 통해 모든 스레드가 항상 최신 값을 볼 수 있도록 한다고 한다.
이제 내가 사용하던 문제가 있는 코드를 소개하자면 다음과 같다.
public class Hello {
private static Hello INSTANCE;
private Hello() {}
public static Hello getInstance() {
if (INSTANCE == null) INSTANCE = new Hello();
return INSTANCE;
}
}
이거였고,
코칭 받아 수정된 코드는 다음과 같다.
class Hello {
private static volatile Hello INSTANCE;
private Hello() {}
public static Hello getInstance() {
if (INSTANCE == null) {
synchronized (Hello.class) {
if (INSTANCE == null) INSTANCE = new Hello();
}
}
return INSTANCE;
}
}
그리고 책에서 본 코드는
class Hello {
public static class SingleInstanceHolder {
private static final Hello INSTANCE = new Hello();
}
public static Hello getInstance() {
return SingleInstanceHolder.INSTANCE;
}
}
이거였다.
내가 사용하는 코드는
Double-checked Locking 패턴을 사용한 방식이고 (약간 잘못 사용하고 있었지만 🥲)
책에서 본 코드는
Initialization on Demand Holder 패턴을 사용한 방식이라고 한다.
이 패턴을 사용하면 간단하게 스레드 안전성을 보장받을 수 있다.
챗봇은 Initialization on Demand Holder 패턴의 단점으로
코드 가독성의 어려움을 꼽았는데,
내가 봤을 땐 Double-checked Locking 패턴이 더 어려운 것 같다..ㅋㅋㅋ ㅠㅠ
싱글톤 패턴을 얼마나 사용 하게 될 지는 모르겠지만..
그래도 잘못 알고있었던 부분에 대해 바로 잡을 수 있어서 좋은 기회였다고 생각한다. 🤓