본문 바로가기
디자인 패턴

싱글톤(Singleton) 패턴 이해하기

by simplify-len 2022. 2. 6.

인스턴스를 오직 한개만 제공하는 클래스

1. 간단한 싱글톤 구현

package me.likelen.study.singleton;

public class Settings1 {

    private static Settings1 instance;

    private Settings1() {
    }

    public static Settings1 getInstance() {
        if (instance == null) {
            instance = new Settings1();
        }
        return instance;
    }
}

질문

1.  생성자를 private 으로 만든 이유는 무엇인가요?

오직 한 개의 인스턴스에만 접근하기 위해 생성자의 노출을 막기 위해서입니다.

 

2. getInstance() 메소드를 static으로 선언한 이유는 무엇인가요?

글로벌하게 접근하게 만들기 위해 static 으로 선언하고, 이는 JVM에 클래스 영역에 생성되어 글로벌하게 사용할 수 있습니다.

 

3. getIntance()가 멀티쓰레드 환경에서 안전하지 않는 이유는 무엇인가요?

멀티쓰레드 환경에서는 오직 한 개의 인스턴스가 아니게 됩니다.

 새롭게 생성된 인스턴스에서도 instance가 null인지 여부를 판단하게 될 텐데요. 이때 새로운 스레드에는 생성된 instance 가 없기 때문에 한 개의 인스턴스를 보장할 수 없습니다.

 

 

2. 동기화(synchronized)를 사용해 멀티쓰레드 환경에 안전하게 만드는 방법

package me.likelen.study.singleton;

public class Settings2 {

    private static Settings2 instance;

    private Settings2() {
    }

    public static synchronized Settings2 getInstance() {
        if (instance == null) {
            instance = new Settings2();
        }

        return instance;
    }
}

질문

1. 자바의 동기화 블럭 처리 방법은 어떻게 되는가요?

정확하게는 메서드에 synchronized 하는데, 메소드 전체가 임계영역으로 설정됩니다. 임계 영역으로 설정된 부분은 쓰레드가 synchronized 메소드가 호출된 시점부터 해당 메소드가 포함된 객체의 Lock을 얻어 작업을 수행하다가 메소드가 종료되면 Lock을 반환합니다.

2. getIntance() 메소드 동기화시 사용하는 락(lock)은 인스턴스의 락인가? 클래스의 락인가요? 그 이유는?

클래스의 락입니다. 만약 락이 인스턴스의 락이라면, 동기화시 하나의 객체를 보장할 수 없게 되기 때문입니다.

3. 이른 초기화(eager initialization)을 사용하는 방법

package me.likelen.study.singleton;

public class Settings3_2 {

    private static final Settings3_2 INSTANCE = new Settings3_2();

    private Settings3_2() {
    }

    public static Settings3_2 getInstance() {
        return INSTANCE;
    }
}

synchronized 가 필요하지 않게 된다.

 

질문

1. 이른 초기화가 단점이 될 수도 있는 이유는 무엇인가요?

미리 만들게 되는 것이 단점이 될 수 있는데, 만약 생성자에 많은 리소스를 사용되는 경우에는 좋지 않다.

 

2. 만약에 생성자에서 checked 예외를 던진다면 이 코드는 어떻게 변경해야 될까요?

만약 생성자에서 예외를 던진다면, 그 안에서 try-catch 으로 예외 핸들링을 해야만 합니다. 그렇지 않다면, 이른 초기화를 사용할 수 없습니다.

4. double checked locking으로 효율적인 동기화 블럭 만들기

package me.likelen.study.singleton;

public class Settings3 {

    private static volatile Settings3 instance;

    private Settings3() {
    }

    public static Settings3 getInstance() {
        if (instance == null) {
            synchronized (Settings3.class) {
                if (instance == null){
                    instance = new Settings3();
                }
            }
        }
        return instance;
    }
}

질문

1. double check locking 이라고 부르는 이유는 무엇인가요?

2번에 걸쳐 단 하나의 객체임을 체크하기 때문입니다.

if 문이 2번 사용되는데, 첫번째는 instance가 null일 경우와, 동기화 클래스 synchronized(xx.class) 에서 한번 더 체크하기 때문입니다.

동기화 매커니즘을 사용하지 않습니다. 만약 instance가 있다면 바로 리턴하기 때문입니다.

2. instance 변수는 어떻게 정의해야 하는가? 그 이유는 무엇인가요?

volatile 을 사용하여 가장 최신의 객체를 가져오도록 합니다. Multi Thread 환경에서 하나의 Thread만 read & write하고 나머지 Thread가 read하는 상황에서 가장 최신의 값을 보장합니다.

 

5. static inner 클래스를 사용하는 방법

package me.likelen.study.singleton;

public class Settings4 {

    private static volatile Settings4 instance;

    private Settings4() {
    }

    private static class Settings4Holder {
        private static final Settings4 INSTANCE = new Settings4();
    }

    private static Settings4 getInstance() {
        return Settings4Holder.INSTANCE;
    }
}

질문

1. 이 방법은 static final 를 썼는데도 왜 지연 초기화(lazy initialization)이라고 볼 수 있을까요?

Holder를 통해 객체를 생성하게 되는데, 이렇게 할 경우 getIntance() 가 호출될 때 로딩되기 때문입니다.

6. enum을 사용하는 방법

package me.likelen.study.singleton;

public enum Settings5 {

    INSTANCE;
}

질문

1. enum 타입의 인스턴스를 리플렉션을 통해 만들 수 있는가?

만들 수 없습니다.  enum 타입의 클래스는 리플랙션을 통해 만들 수 없도록 제한합니다.

 

2. enum으로 싱글톤 타입을 구현할 때의 단점은?

단점은 이른 초기화와 같이 미리 만들어진다는 것입니다. 그리고 상속을 사용할 수 없습니다.

 

3. 직렬화&역직렬화 시에 별도로 구현해야 하는 메소드가 있는가?

별다른 장치가 없어도 Enum 클래스는 직렬화&역직렬화가 됩니다. 그러나,  getResolves() 구현시 역직렬화시 변경을 가할 수 있습니다.

 

 

권장되는 두 가지는 Holder 를 사용하는 것과, Enum 을 사용하는 것입니다.

 

 

 

[출처]

 

코딩으로 학습하는 GoF의 디자인 패턴-백기선

 

Java Thread synchronized (동기화)

 

Java Thread synchronized (동기화)

Java Thread Synchronized(동기화)란 여러 개의 Thread가 한 개의 자원을 사용하고자 할 때, 해당 Thread만 제외하고 나머지는 접근을 못하도록 막는 것이다. Multi-Thread로 인하여 동기화를 제어해야하는 경

link2me.tistory.com

Java volatile이란?

 

Java volatile이란?

 

nesoy.github.io

 

댓글