본문으로 건너뛰기

디자인 패턴: 싱글톤 패턴 (Singleton Pattern)

무료2015-03-06#Design_Pattern#单件模式#单例模式#Singleton_Pattern

싱글톤 패턴은 단일체 패턴이라고도 불리며, 정의는 매우 간단합니다—지정된 클래스의 인스턴스를 하나만 존재하도록 하고, 해당 클래스가 전역 액세스 포인트를 제공하는 것입니다. 그러나 실제 적용 시에는 많은 문제에 직면하게 됩니다. 예를 들어, 멀티스레드 환경이나 여러 클래스로더 환경에서 싱글톤 패턴의 정확성을 어떻게 보장할지 등입니다

no_mkd

일.싱글톤 패턴이란 무엇인가?

싱글톤 패턴은 단일체 패턴이라고도 불리며, 그 목적은 단적으로 말해 '해당 클래스의 인스턴스가 하나만 존재하도록 보장하는' 것입니다

싱글톤 패턴은 리소스에 민감한 객체를 관리하기 위해 자주 사용됩니다. 예를 들어: 데이터베이스 연결 객체, 레지스트리 객체, 스레드 풀 객체 등입니다. 이러한 객체들이 동시에 여러 개 존재하면 다양한 불일치한 문제가 발생합니다 (데이터베이스 중복 연결 오류가 발생하는 것을 원하지 않겠죠)

이.클래스의 인스턴스가 하나만 있음을 어떻게 보장하는가?

(이 문제는 간단해 보이지만, 싱글톤 패턴을 접한 적이 없다면 해결책을 스스로 생각해내기 위해서는 어느 정도의 재능이 필요합니다..믿기지 않는다면, 한번 생각해 보세요..)

1.클래스의 인스턴스는 하나만 될 수 있습니까? 클래스 이름만 알면 마음대로 new 할 수 있지 않습니까?
물론 가능합니다. 클래스 이름을 알면 해당 생성자를 호출하여 new 할 수 있습니다. 그러나 한 가지를 놓쳤습니다: 그 클래스에는 퍼블릭 생성자가 필요합니다.不是吗?

2.즉, 클래스의 인스턴스를 하나만 보장하려면, 그 클래스에 퍼블릭 생성자가 있으면 안 됩니다.对吧?
맞습니다. 그렇게 하려면 프라이빗 생성자를 정의해야 합니다

3.퍼블릭 생성자가 없는 클래스는 인스턴스를 생성할 수 있습니까? 생성자가 private 이라면, 해당 클래스의 인스턴스만이 이 생성자를 호출할 수 있습니다. 마찬가지로, 이 생성자를 호출해야만 해당 클래스의 인스턴스를 생성할 수 있습니다..이는 '닭이 알을 낳고, 알이 닭을 낳는..' 문제가 아닙니까? 프라이빗 생성자라도 물론 인스턴스를 생성할 수 있습니다. 위에서 한 가지를 놓쳤습니다: '해당 클래스의 인스턴스만이 이 생성자를 호출할 수 있는' 것은 아닙니다. 클래스 내부에서는 인스턴스를 생성하지 않아도 자유롭게 이 프라이빗 생성자를 호출할 수 있기 때문입니다

위의 논의 결과가 있으면, 클래식한 싱글톤 패턴을 구현할 수 있습니다:

package SingletonPattern;

/**

  • @author ayqy

  • 가장 클래식한 싱글톤 패턴 */ public class Singleton {

    private static Singleton instance;//정적 인스턴스 변수 정의

    /**

    • 프라이빗 생성자를 정의하여 외부에서 new 방지 */ private Singleton(){ //초기화 처리 }

    /**

    • 전역 액세스 포인트 제공
    • @return 클래스의 인스턴스 */ public static Singleton getInstance(){ if(instance == null) instance = new Singleton(); return instance; }

    /*

    • 기타 유용한 속성과 동작
    • 싱글톤 패턴을 적용한 클래스도 본래의 기능을 유지한다
    • */ }

주의: 마지막으로 한 가지를 분명히 이해해야 합니다. 싱글톤 패턴을 적용한 클래스는 본래의 기능을 잃어서는 안 됩니다. 사용하기 위해 사용해서는 안 됩니다

삼.싱글톤 패턴을 계속 생각해보자

우리의 싱글톤 패턴은 완벽하게 안전할까요? 아닙니다. 아직 많은 문제가 있습니다. 예를 들어:

1.멀티스레드 환경에서; 2.여러 class loader 환경에서

생성되는 인스턴스가 하나뿐임을 보장할 수 없습니다,对吧?

그러나 성숙한 디자인 패턴으로서, 싱글톤 패턴은 이러한 환경에 대응할 수 있어야 합니다. 따라서, 다음으로 이러한 환경에 어떻게 대응하는지 논의하겠습니다

사.멀티스레드 환경에서의 싱글톤 패턴

멀티스레드 환경에서 인스턴스의 유일성을 어떻게 보장할까? synchronized 키워드를 사용하여 스레드 안전을 보장하는 것을 쉽게 생각할 수 있습니다. 다음과 같이:

/**
 * 전역 액세스 포인트 제공
 * @return 클래스의 인스턴스
 */
public static synchronized Singleton getInstance(){
	if(instance == null)
		instance = new Singleton();
	return instance;
}

getInstance 메서드를 동기화 메서드로 정의하면 여러 스레드가 동시에 이 메서드에 들어가는 것을 방지하여 서로 다른 인스턴스가 생성되는 것을 방지합니다

그러나 위의 방법에는 치명적인 문제가 있습니다: synchronized 키워드로 메서드를 동기화하면 효율이 크게 감소합니다 (메서드 하나를 동기화하면 효율이 백 배 감소할 수도 있습니다..). 이렇게 하면 프로그램이拖累됩니다. 더 좋은 개선 방법이 있을까요?

먼저, 위의 동기화 블록은 getIntance 메서드 전체입니다. 이 메서드를 호출할 때마다 강제로 동기화 메커니즘에 들어가지만, 잘 생각해보면, 이 메서드를 처음 호출할 때만 동기화가 필요합니다 (첫 번째 객체 new). 이후 호출에서는 new 된 객체를 반환하기만 하면 됩니다

따라서, 개선안은: 이중 잠금을 사용하여 첫 번째 객체 new 시에만 동기화를 수행하는 것입니다

package SingletonPattern;

/**

  • @author ayqy

  • 멀티스레드 하의 싱글톤 패턴 2——이중 잠금을 사용하여 객체 인스턴스화 시에만 동기화 보장 */ public class DoubleLockSingleton {

    private static volatile DoubleLockSingleton instance;//정적 인스턴스 변수 정의

    /**

    • 프라이빗 생성자를 정의하여 외부에서 new 방지 */ private DoubleLockSingleton(){ //초기화 처리 }

    /**

    • 전역 액세스 포인트 제공
    • @return 클래스의 인스턴스 */ public static DoubleLockSingleton getInstance(){ if(instance == null) synchronized(DoubleLockSingleton.class){//동기화 블록 진입 if(instance == null)//다시 null 체크 instance = new DoubleLockSingleton(); } return instance; }

    /*

    • 기타 유용한 속성과 동작
    • 싱글톤 패턴을 적용한 클래스도 본래의 기능을 유지한다
    • */ }

주의: 이중 잠금은 volatile 키워드 (컴파일러에 이 변수의 복사본을 유지하지 않도록 지시. 변경이 발생하면 강제로 다시 기록되어 불일치 방지) 와 synchronized 로 수식된 동기화 블록에 구현됩니다

그러나 이렇게 하는 데는 대가가 있습니다. volatile 키워드는 컴파일러에 이 객체에 대한 컴파일 최적화를 수행하지 않도록 지시합니다. 첫 번째 new 객체 프로세스만 보면, 이중 잠금의 효율은 동기화 메서드보다 낮지만, 이중 잠금 방식은 이후 호출에서는 동기화가 필요하지 않으므로, 장기적으로 보면 이중 잠금의 효율이 동기화 메서드보다 높습니다

느린 동기화 메커니즘을 사용하지 않고도 스레드 안전을 보장할 수 있는 방법이 있을까요? 만약 있다면, 효율을 크게 향상시킬 수 있을 것입니다.对吧?

물론 있습니다. 이 방법은 "조기 초기화"라고 합니다 (덧붙여 말하면, 앞에서 언급한 "클래식한 싱글톤 패턴"은 실제로 "지연 초기화" 방법을 사용했습니다..매우 간단하여 설명할 필요도 없습니다). 함께 살펴보겠습니다:

package SingletonPattern;

/**

  • @author ayqy

  • 멀티스레드 환경 하의 싱글톤 패턴——"조기 초기화"를 사용하여 스레드 안전 보장 */ public class EagerlyInitSingleton {

    private static EagerlyInitSingleton instance = new EagerlyInitSingleton();//정적 인스턴스 변수 정의 및 클래스 로딩 시 초기화 처리 실행

    /**

    • 프라이빗 생성자를 정의하여 외부에서 new 방지 */ private EagerlyInitSingleton(){ //초기화 처리 }

    /**

    • 전역 액세스 포인트 제공
    • @return 클래스의 인스턴스 */ public static synchronized EagerlyInitSingleton getInstance(){ return instance; }

    /*

    • 기타 유용한 속성과 동작
    • 싱글톤 패턴을 적용한 클래스도 본래의 기능을 유지한다
    • */ }

어, 이것도 방법이라고 할 수 있을까요? 이렇게 하면 표준에 맞지 않는 것 같습니다..没关系, 이 방법에도 물론 장점이 있습니다. 예를 들어:

1.효율이 매우 높고 스레드 안전; 2.단순하고 사용하기 쉬우며, 아무것도 고려할 필요가 없고, 심지어 판단도 불필요

그러나 치명적인 단점은: 리소스 낭비 문제입니다. 만약 이 객체가 거대하고 극도로 리소스를 소모하는 객체라면, 처음에 생성해놓고迟迟 사용하지 않는다면, 이는 매우 아픈 일입니다..

위에서 세 가지 스레드 동기화 보장 방법을 언급했습니다. 어떻게 선택할지는 구체적인 상황에 따라 결정해야 합니다. 효율, 리소스 활용 등 다양한 요소를 종합적으로 고려해야 합니다

오.여러 class loader 환경에서의 싱글톤 패턴

여러 클래스로더가 존재하는 경우, 여러 클래스로더가 동시에 싱글톤 클래스를 로드하여 여러 인스턴스를 생성할 수 있습니다

이러한 상황에 대해서는, 어떤 class loader 를 사용하여 싱글톤 클래스를 로드할지 명시적으로 지정함으로써, 위의 문제를 효과적으로 피할 수 있습니다

육.요약

싱글톤 패턴을 적용하면 객체의 유일성을 보장할 수 있습니다. 그러나 싱글톤 패턴의 적용 범위에 주의해야 합니다. 싱글톤 패턴을 남용해서는 안 됩니다. 관리해야 할 리소스에 민감한 객체가 많지 않기 때문입니다

댓글

아직 댓글이 없습니다

댓글 작성