컴퓨터과학/0 + 소프트웨어 아키텍처(디자인 패턴)

[소프트웨어 아키텍처] 11. 싱글톤 패턴(Singleton Pattern -java)

힘들면힘을내는쿼카 2022. 12. 6. 17:32
728x90
반응형
싱글톤 패턴(Singleton Pattern)

인스턴스가 하나뿐인 특별한 객체
싱글톤 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공합니다.

 

다른 클래스에서는 싱글톤으로 구현한 클래스 인스턴스를 만들지 못하도록 해야합니다.

 

싱글톤 패턴을 실제로 적용할 때는 클래스에서 하나뿐인 인스턴스를 관리하도록 만들면 됩니다.
그리고 다른 클래스에서도 자신의 인스턴스를 추가로 만들지 못하게 해야합니다.
이말은 인스턴스가 필요하다면 반드시 클래스 자신을 거치도록해야함을 의미합니다.

어디서든 해당 인스턴스에 접근할 수 있도록 전역 접근 지점을 제공합니다.
언제든 인스턴스가 필요하면 클래스에 요청할 수 있게 만들어 놓고, 요청이 들어오면 하나뿐인 인스턴스를 건네주도록 만들어야 합니다.
여기서 싱글톤은 게으른 방식으로 생성되도록 구현할 수도 있습니다.

이러한 방법은 자원을 많이 사용하는 곳에서 유용하겠죠?

싱글톤 패턴 구조

  • uniqueInstance
    • 싱글턴의 하나뿐인 인스턴스가 저장
  • getInstance()
    • 정적메소드(클래스 메소드)로써 Singleton.getInstance()를 사용하면 어디서든 호출이 가능하다.

싱글톤 패턴의 종류

고전적인 싱글톤(게으른 인스턴스 생성)

public class ClassicSingleton {
    //정적 변수
    private static ClassicSingleton uniqueInstance;

    private ClassicSingleton() {
    }

    public static ClassicSingleton getInstance() {
        if (uniqueInstance == null) {
            //게으른 인스턴스 생성(LazyInstantiation)
            uniqueInstance = new ClassicSingleton();
        }
        return uniqueInstance;
    }
}
반응형

 

뭐야 엄청 간단하네?!

하지만, 멀티스레드 환경에서 이 방법에는 문제가 있습니다.

서로 다른 2개의 객체가 리턴되었습니다.. 😭

728x90

멀티스레딩 문제 해결 싱글톤

synchronized

속도가 중요하지 않다면 사용해도 상관 없습니다.
getInstance()를 동기화하는 게 어려운 일도 아니고, 효율도 좋을 수 있습니다.
다만 메소드를 동기화 하면 100배 정도 저하됩니다.
만약에, getInstance()가 애플리케이션에서 병목으로 작용한다면 다른 방법을 생각해야 합니다..!

public class SynchronizedSingleton {
    private static SynchronizedSingleton uniqueInstance;

    private SynchronizedSingleton() {
    }

    /** 멀테스레딩 문제 해결
        * 동기화할 때, 성능면에서 100배정도 느려진다.    
        * 처음을 제외하면 동기화는 불필요한 오버헤드만 증가시킬뿐..
        ** /
    public static synchronized SynchronizedSingleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new SynchronizedSingleton();
        }
        return uniqueInstance;
    }
}

static initializer

인스턴스가 필요할 때는 생성하지말고 처음부터 만듭니다.
처음부터 인스턴스를 만들면 좋습니다.
클래스가 로딩될 때, JVM에서 Singleton의 하나뿐인 인스턴스를 생성해줍니다.
JVM에서 하나뿐인 인스턴스를 생성하기 전까지 그 어떤 스레드도 uniqueInstance 정적 변수에 접근할 수 없습니다.

public class Singleton {
    //정적 초기화 부분에서 인스턴스 생성, 이러면 멀티스레드 환경에서도 별문제가 없다.
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return uniqueInstance;
    }
}

DCL(Double-Checked Locking)

DCL을 사용해서 getInstance()에서 동기화되는 부분을 줄입니다.
DCL(Double-Checked Locking)을 사용하면 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다. 이러면 처음에만 동기화하고 나중에는 동기화하지 않아도 됩니다…!

public class DCLSingleton {
    //volatile는 Java 변수를 Main Memory에 저장
    private volatile static DCLSingleton uniqueInstance;

    /**
     * DCL(Double-Checked Locking)을 사용하면
     * 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다.
     * 이러면 처음에만 동기화를 진행하고 나중에는 동기화 하지 않아도 됩니다.
     */
    public static DCLSingleton getInstance() {
        if(uniqueInstance == null) { //인스턴스가 있는지 확인하고, 없으면 동기화된 블록으로..
            synchronized (DCLSingleton.class) {
                if(uniqueInstance == null) {
                    uniqueInstance = new DCLSingleton();
                }
            }
        }
        return uniqueInstance;
    }
}

하지만,,,,,,,, 위와 같은 방법들은 동기화, 클래스 로딩, 리플렉션, 직렬화, 역직렬화 문제들이 발생할 수 있습니다..

클래스 로더

  • 클래스 로더 2개가 각기 다른 싱글턴의 인스턴스를 가지게 된다면?
    • 클래스 로더마다 서로 다른 네임스페이스를 정의하기 때문에 클래스 로더가 2개 이상이라면 같은 클래스를 여러 번 로딩할 수도 있습니다. 그러니 싱글턴을 그런 식으로 로딩하면 인스턴스가 여러 개 만들어지는 문제가 발생합니다.이러한 경우에는 직접 클래스 로더를 지정하면 해결 가능합니다.

 

리플렉션(Reflection), 직렬화(Serializable), 역직렬화(Deserialzation)

출처: JAVA Reflection & Serializable :: Mhwan’s Story

  • 리플렉션
    • 리플렉션은 run-time에 동적으로 클래스 정보를 알아내고, 실행할 수 있는 것을 의미합니다.
      • 객체를 통해 그 객체의 원래 클래스와 그에 따르는 여러 정보들을 알아내는 방법
    • 일반적인 객체 생성은 compile-time에 우리가 작성한 클래스와 메소드가 컴파일되어 JVM의 메모리 영역에 올라와 있는데, 리플렉션은 그점이 아니라 run-time에 되는것이 가장 큰 차이점 입니다.
      • 동적으로 실행(동작 및 수정) 할 수 있게 되는 것을 의미 합니다.
      • IDE에서 자동완성 기능이 이에 해당합니다.^^
    • 리플렉션을 통해 클래스를 분석하고(객체의 생성자, 변수, 메소드 private 포함) 객체를 만들고, 메소드를 실행할 수도 있게 됩니다.
    • 출처: 리플렉션(Reflection) -1
  • 직렬화
    • java 시스템 내부에서 사용되는 객체나 데이터를 외부의 자바 시스템에서 이용할 수 있도록 byte 형태로 변환하는 기술 입니다.
    • Serializable을 이용할때는 serialVersionUID 값을 설정하기를 권장 합니다.
    • serialVersionUID 값은 해당 클래스의 버전이 서로 맞는지 체크하는 용도로 사용합니다.
      • 이를 설정하지 않으면 JVM에서 자동으로 생성하는데, OS에 따라 JVM이 달라지기 때문에 예외가 발생할수 있습니다.
        • java의 직렬화 대상은 동일한 serialVersionUID을 가지고 있어야 합니다.!!
  • 역직렬화
    • 외부의 자바 시스템의 byte 형태의 데이터를 java 시스템 내부에서 사용되는 객체나 데이터로 변환하는 기술 입니다.

 

 

그러면 어떻게 사용하라는거야?!!

 

Enum

enum을 사용하면 모든 문제를 해결하고 완벽한 싱글톤 패턴을 구현할 수 있습니다.

public enum EnumSingleton {
    UNIQUE_INSTANCE;
}

public class SingletonClient {
    public static void main(String[] args) {
        EnumSingleton enumSingleton = EnumSingleton.UNIQUE_INSTANCE;
    }
}

정리

  • 클래스에 싱글톤 패턴을 적용하면 1개의 인스턴만 생성된다.
  • 싱글톤 패턴의 인스턴스는 어디서든지 접근 가능하다.
  • javaenum을 사용하면 간단하게 구현할 수 있다.

 

 

728x90
반응형