반응형

의도

  • 오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공한다.

 

활용성

  • 클래스의 인스턴스가 오직 하나여야 함을 보장하고, 잘 정의된 접근점(acces point)으로 모든 사용자가 접근할 수 있도록 해야 할 때
  • 유일한 인스턴스가 서브클래싱으로 확장되어야 하며, 사용자는 코드의 수정없이 확장된 서브클래스의 인스턴스를 사용할 수 있어야 할 때

 

결과

  • 유일하게 존재하는 인스턴스로의 접근을 통제한다.
    • Singleton 클래스 자체가 인스턴스를 캡슐화하기 때문에, 이 클래스에서 사용자가 언제, 어떻게 이 인스턴스에 접근할 수 있는지 제어할 수 있다.
  • 이름 공간(name space)을 좁힌다.
    • 단일체 패턴은 전역 변수보다 더 좋다.
    • 전역 변수를 사용해서 이름 공간을 망치는 일을 없애준다. 즉, 디버깅의 어려움 등의 문제를 없앤다.
  • 연산 및 표현의 정제를 허용한다.
    • Singleton 클래스는 상속될 수 있기 때문에, 이 상속된 서브클래스를 통해서 새로운 인스턴스를 만들 수 있다.
    • 또한 이 패턴을 사용하면, 런타임에 필요한 클래스의 인스턴스를 써서 응용프로그램을 구성할 수도 있다.
  • 인스턴스의 개수를 변경하기가 자유롭다.
    • Singleton 클래스의 인스턴스가 하나 이상 존재할 수 있도록 변경해야 할 경우에 이 작업도 어렵지 않다.
    • Singleton 클래스의 인스턴스에 접근할 수 있는 허용 범위를 결정하는 연산만 변경하면 된다.
      • 기존에는 하나의 인스턴스로만 접근을 허용했다면, 이제는 여러 개의 인스턴스를 생성해서 그 각각의 인스턴스로 접근할 수 있도록 연산의 구현을 바꾸면 되기 때문이다.

 

협력 방법

  • 사용자는 Singleton 클래스에 정의된 Instance() 연산을 통해서 유일하게 생성되는 단일체 인스턴스에 접근할 수 있다.

 

구조

 

실제 구현 구조

 

구현 - Lazy initialization(지연 초기화)

  • 인스턴스가 필요한 시점에 인스턴스를 생성한다.
  • 쓰레드에 안전하지 않다.
    • 동시에 인스턴스 생성이 호출되어 인스턴스가 여러 개 생성될 수 있다.

소스코드

//Singleton
public class lazyInitPrinter {
    private static lazyInitPrinter printer;

    private lazyInitPrinter() {}

    public static lazyInitPrinter getInstance() {
        if(printer == null) {
            printer = new lazyInitPrinter();
        }
        return printer;
    }

    public void print(String string) {
        System.out.println("[lazyInitPrinter] " + string);
    }
}
public class Main {

    public static void main(String[] args) {
        PrinterSelecter printerSelecter = new PrinterSelecter();

        Thread thread1 = new Thread(() -> {
            for(int i = 0; i < 5 ; i++) {
                System.out.println(printerSelecter.getLazyInitPrinter() + " : " + Thread.currentThread().getName());
            }
        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            for(int i = 0; i < 5 ; i++) {
                System.out.println(printerSelecter.getLazyInitPrinter() + " : " + Thread.currentThread().getName());
            }
        });
        thread2.start();

        System.out.println(printerSelecter.getLazyInitPrinter() + " : " + Thread.currentThread().getName());
    }
}
  • 멀티 스레드 환경에서 싱글톤 객체의 인스턴스 생성이 동시에 여러 번 호출 되어 하나의 인스턴스만 가져야하는 싱글톤 객체가 어느 시점에 여러 개의 인스턴스를 가지고 있다.

 

구현 - Lazy initialization with synchronized (동기화 블럭 사용한 지연 초기화)

  • 동기화 블럭을 사용하여 지연 초기화 방식의 싱글톤 패턴을 멀티쓰레드에 안전하도록 개선
  • 그러나 synchronized 키워드를 사용하면 성능이 감소한다.

소스코드

//Singleton
public class threadSafeLazyInitPrinter {
    private static threadSafeLazyInitPrinter printer;

    private threadSafeLazyInitPrinter() {}

    public static synchronized threadSafeLazyInitPrinter getInstance() {
        if(printer == null) {
            printer = new threadSafeLazyInitPrinter();
        }
        return printer;
    }

    public void print(String string) {
        System.out.println("[threadSafeLazyInitPrinter]" + string);
    }
}
public class Main {

    public static void main(String[] args) {
        PrinterSelecter printerSelecter = new PrinterSelecter();

        Thread thread1 = new Thread(() -> {
            for(int i = 0; i < 5 ; i++) {
                System.out.println(printerSelecter.getThreadSafeLazyInitPrinter() + " : " + Thread.currentThread().getName());
            }
        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            for(int i = 0; i < 5 ; i++) {
                System.out.println(printerSelecter.getThreadSafeLazyInitPrinter() + " : " + Thread.currentThread().getName());
            }
        });
        thread2.start();

        System.out.println(printerSelecter.getThreadSafeLazyInitPrinter() + " : " + Thread.currentThread().getName());
    }
}

 

구현 - Lazy Initialization. LazyHolder

  • 쓰레드에 안전하며 성능도 좋다.
  • 싱글톤 패턴을 구현할 때 해당 방식을 많이 사용한다.

소스코드

//Singleton
public class LazyHolderPrinter {
    private LazyHolderPrinter() {}

    private static class InnerInstanceClazz {
        // 클래스 로딩 시점에서 생성
        private static final LazyHolderPrinter uniqueInstance = new LazyHolderPrinter();
    }

    public static LazyHolderPrinter getInstance() {
        return InnerInstanceClazz.uniqueInstance;
    }
}
public class Main {

    public static void main(String[] args) {
        PrinterSelecter printerSelecter = new PrinterSelecter();

        Thread thread1 = new Thread(() -> {
            for(int i = 0; i < 5 ; i++) {
                System.out.println(printerSelecter.getLazyHolderPrinter() + " : " + Thread.currentThread().getName());
            }
        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            for(int i = 0; i < 5 ; i++) {
                System.out.println(printerSelecter.getLazyHolderPrinter() + " : " + Thread.currentThread().getName());
            }
        });
        thread2.start();

        System.out.println(printerSelecter.getLazyHolderPrinter() + " : " + Thread.currentThread().getName());
    }
}

 

  • LazyHolderPrinter 클래스에는 InnerInstanceClazz 클래스의 변수가 없기 때문에, static 멤버 클래스더라도 클래스 로더가 초기화 과정을 진행할 때 InnerInstanceClazz 메서드를 초기화 하지 않고, getInstance() 메서드를 호출할 때 초기화 된다.
    • 동적 바인딩(Dynamic Binding)의 특징을 이용하여 쓰레드에 안전하면서 성능이 뛰어나다.
    • InnerInstanceClazz 내부 인스턴스는 static 이기 때문에 클래스 로딩 시점에 한 번만 호출된다는 점을 이용한 것이며, final 키워드를 사용하여 다시 값이 할당되지 않도록 한다.

 

관련 패턴

  • 많은 패턴이 단일체 패턴으로 구현될 수 있다.
    • 추상 팩토리, 빌더, 원형 패턴

 


[참고자료]

리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)

http://www.cs.unc.edu/~stotts/GOF/hires/pat3dfso.htm

https://blog.seotory.com/post/java-singleton-pattern

https://medium.com/webeveloper/싱글턴-패턴-singleton-pattern-db75ed29c36

 

반응형

+ Recent posts