티스토리 뷰

Singleton pattern (싱글턴 패턴)

어떤 클래스가 최초 한번만 메모리를 할당하고 (static), 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴
생성자가 여러번 호출되어도, 실제로 생성되는 객체는 하나이고 최초에 생성한 객체를 반환한다.
즉, 단 하나의 인스턴스를 생성해 사용하는 패턴

이유, 장점

  • 한번의 new로 고정된 메모리 영역의 인스턴스를 사용하기 때문에 메모리 낭비를 방지할 수 있다.
  • 싱글톤으로 만들어진 클래스의 인스턴스는 global이기 때문에 다른 곳에서 데이터를 공유하기 쉽다.
  • DB Connection Pool (DBCP)처럼 공통된 객체를 여러개 생성해 사용해야하는 상황에서 많이 사용.
  • 안드로이드의 경우, 각 액티비티나 클래스 별로 주요 클래스를 일일히 전달하기 번거로우므로 싱글톤 클래스를 만들어 어디서나 쉽게 접근가능하도록 설계하는것이 편하기 때문
  • 인스턴스가 절대적으로 한개만 존재하는 것을 보증할때 사용한다. 무분별한 객체생성 방지
  • 2번째 이용부터 객체로딩시간이 줄어 성능 향상

단점, 문제점

  • 싱글톤 인스턴스의 비중이 너무 커져 다른 클래스의 인스턴스들과 결합성이 너무 단단해질수있다. 이 경우 당연히 수정과 테스트가 어려워짐. 장점이었던 global이라는 점이 동시에 단점.
  • 멀티스레드 환경에서 동기화처리를 하지않으면 인스턴스가 2개 생성될수있다

19.9.29 추가

Java Singleton Design Pattern Practices with Examples 자료를 함께 보면 좋다.
본 포스팅과 같은 흐름으로, 점진적인 개선과 장단점이 잘 설명되어 있음.


0. Thread-safe하지 않은 코드

public class ThreadSafeLazyInitialization{
    private static ThreadSafeLazyInitialization instance;
    private ThreadSafeLazyInitialization(){}

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

위의 코드는 멀티스레드 환경에서 위험하다. 동시에 접근하다가 하나만 생성되야하는 인스턴스가 2개 생성될 수 있기 때문!! 따라서 아래의 1번과 같이 synchronized 키워드를 추가해 동기화 시킨다.

멀티스레드에서 안전한 (Thread-safe) 싱글톤 클래스, 인스턴스 만드는 방법

1. Thread-safe Lazy Initialization (게으른 초기화)

public class ThreadSafeLazyInitialization{
    private static ThreadSafeLazyInitialization instance;
    private ThreadSafeLazyInitialization(){}

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

private static으로 인스턴스 변수를 만들고, private 생성자로 외부생성을 막는다.
synchronized 키워드로 thread-safe하게 만들었으나, synchronized 특성상 큰 성능저하가 발생하므로 권장되지 않는 방법.

2. Thread-safe Lazy Initialization + Double-checked Locking

public class ThreadSafeLazyInitialization {
    private volatile static ThreadSafeLazyInitialization instance;
    private ThreadSafeLazyInitialization(){}

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

게으른 초기화 방식의 성능저하를 완화시키는 방법

getInstance()에 synchronized하는것이 아니라, 첫번째 if문으로 인스턴스 존재여부를 확인하고 두번째if문으로 다시한번 체크할때 동기화시켜서 인스턴스를 생성한다. 따라서 thread-safe하고 처음생성이후 synchronized 블럭을 타지않으므로 성능저하를 완화. 하지만 이 방법은 멀티코어환경에서 한cpu를 제외한 나머지에 Lock이 걸리므로, 완벽한 방법은 아니다.

( volatile 키워드 : https://nesoy.github.io/articles/2018-06/Java-volatile )

3. Initialization on demand holder idiom

public class Something {
    private Something() {
    }

    private static class LazyHolder {
        public static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

중첩클래스를 이용해, class안에 (Holder)class를 두어, JVM의 class loader 메커니즘과 class가 로드되는 시점을 이용한 방법

개발자가 직접 동기화 코드를 작성하고 문제를 다루려하면, 프로그램 구조가 복잡해지고 비용이 증가하며 안정성이 보장되지 않는다.
이 방법은 JVM의 클래스 초기화 과정에서 보장되는 원자적(atomic) 특성을 이용하여, 싱글톤 초기화 문제에 대한 책임을 JVM에게 떠넘긴다.
holder안에 선언된 인스턴스가 static이므로 클래스 로딩시점에 한번만 호출될것이고, final을 이용해 값의 재할당을 막는다.

가장 일반적이고 많이 사용되는 방법이라고 함.

4. 참고링크를 보면 enum을 이용한 방법 등 더 존재한다.

참고링크

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/

https://jeong-pro.tistory.com/86
https://asfirstalways.tistory.com/335
https://blog.seotory.com/post/2016/03/java-singleton-pattern
https://mygumi.tistory.com/265?category=721096

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함