singleton 4대 디자인 - Java에서 싱글 톤 패턴을 구현하는 효율적인 방법은 무엇입니까?





15 Answers

사용법에 따라 몇 가지 "올바른"대답이 있습니다.

java5는 최선의 방법은 enum을 사용하는 것입니다.

public enum Foo {
   INSTANCE;
}

Pre java5, 가장 간단한 경우는 다음과 같습니다.

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

코드를 살펴 보겠습니다. 먼저, 수업을 끝내고 싶습니다. 이 경우 final 키워드를 사용하여 사용자에게 final 키워드임을 알립니다. 그런 다음 사용자가 자신의 Foo를 만들지 못하도록 생성자를 비공개로 만들 필요가 있습니다. 생성자에서 예외를 throw하면 사용자가 리플렉션을 사용하여 두 번째 Foo를 만드는 것을 방지 할 수 있습니다. 그런 다음, 유일한 인스턴스를 보유하는 private static final Foo 필드를 작성하고이를 리턴하는 public static Foo getInstance() 메소드를 작성합니다. Java 스펙은 클래스가 처음 사용될 때만 생성자가 호출되도록합니다.

매우 큰 객체 또는 무거운 구조 코드가 있고 인스턴스가 필요하기 전에 사용할 수있는 다른 액세스 가능한 정적 메서드 나 필드가있는 경우에는 지연 초기화를 사용해야합니다.

private static class 를 사용하여 인스턴스를로드 할 수 있습니다. 코드는 다음과 같이 보입니다.

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

이후 라인 private static final Foo INSTANCE = new Foo(); 클래스 FooLoader가 실제로 사용될 때만 실행됩니다. 이것은 게으른 인스턴스화를 처리하고 스레드로부터 안전하다는 것을 보장합니다.

개체를 serialize 할 수도있게하려면 deserialization에서 복사본을 만들지 않아야합니다.

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

메소드 readResolve() 는 객체가 이전 프로그램 실행에서 직렬화 된 경우에도 유일한 인스턴스가 리턴 될 것임을 확인합니다.

dao 싱글톤 c++

Java에서 싱글 톤 패턴을 구현하는 효율적인 방법은 무엇입니까?




Stu Thompson이 게시 한 솔루션은 Java5.0 이상에서 유효합니다. 하지만 오류가 발생하기 쉽기 때문에 사용하지 않는 것이 좋습니다.

휘발성 선언문을 잊어 버리고 필요한 이유를 이해하기 어렵습니다. 휘발성이 없으면이 코드는 double-checked locking antipattern 때문에 더 이상 스레드 안전하지 않습니다. Java Concurrency in Practice의 16.2.4 단락에서 더 자세히 살펴보십시오. 즉,이 패턴 (Java5.0 이전 또는 휘발성 문이없는)은 잘못된 상태의 (여전히) Bar 객체에 대한 참조를 반환 할 수 있습니다.

이 패턴은 성능 최적화를 위해 고안되었습니다. 그러나 이것은 정말로 더 이상 진짜 관심사가 아닙니다. 다음과 같은 게으른 초기화 코드는 빠르고 더 중요하게 읽기 쉽습니다.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}



게으른 초기화는 잊어 버리십시오. 문제가 너무 많습니다. 이것은 가장 간단한 솔루션입니다.

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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



싱글 톤은 클래스 로더가로드 한 싱글 톤임을 잊지 마십시오. 여러 로더 (컨테이너)를 사용하는 경우 각각은 고유 한 버전의 Singleton을 가질 수 있습니다.




그것을 작성하기 전에 싱글 톤이 필요한 이유를 정말로 고려하십시오. Java를 사용하여 Google 싱글 톤을 사용하면 쉽게 우연히 발견 할 수있는 준 종교적 논쟁이 있습니다.

개인적으로 나는 싱글 톤을 가능한 한 자주 여러 가지 이유로 피하려고 노력합니다. 싱글 톤은 대부분 싱글 톤으로 검색 할 수 있습니다. 싱글 톤은 모두가 쉽게 이해할 수 있기 때문에 학대 당하고 있다고 생각합니다. OO 디자인에 "글로벌"데이터를 가져 오는 메커니즘으로 사용되며, 객체 라이프 사이클 관리를 회피하기 쉽기 때문에 사용됩니다 (또는 B에서 내부에서 어떻게 할 수 있는지에 대해 정말로 생각해보십시오.) Inversion of Control (IoC) 또는 Dependency Injection (DI)과 같은 멋진 중간계를 살펴보십시오.

당신이 정말로 하나를 필요로한다면 위키 피 디아는 싱글 톤의 적절한 구현의 좋은 예가됩니다.




내 싱글 톤을 관리하기 위해 스프링 프레임 워크를 사용한다. 클래스의 "단일성"을 강요하지는 않지만 (여러 클래스 로더가 관련된 경우에는 실제로 할 수 없습니다) 다양한 유형의 객체를 생성하기위한 여러 팩토리를 빌드하고 구성하는 정말 쉬운 방법을 제공합니다.




Wikipedia는 자바에서도 싱글 톤의 examples 줍니다. Java 5 구현은 꽤 완벽 해 보이며 스레드로부터 안전합니다 (이중 선택 잠금이 적용됨).




나는 Enum singleton이라고 말할 것입니다.

Java에서 enum을 사용하는 싱글 톤은 일반적으로 enum singleton을 선언하는 방법입니다. Enum 싱글 톤에는 인스턴스 변수 및 인스턴스 메서드가 포함될 수 있습니다. 간단하게하기 위해 인스턴스 메서드를 사용하는 경우 인스턴스 메서드를 사용하는 경우 해당 메서드의 스레드 안전성을 보장해야한다는 것보다 객체의 상태에 영향을 주어야합니다.

열거 형의 사용은 구현하기가 매우 쉽고 다른 방법으로 우회되어야하는 직렬화 가능 객체에 대한 단점이 없습니다.

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Singleton.INSTANCE 에서 getInstance() 메서드를 호출하는 것보다 훨씬 쉽게 Singleton.INSTANCE 사용하여 액세스 할 수 있습니다.

1.12 열거 형 상수의 직렬화

Enum 정수는 통상의 직렬화 가능 오브젝트 또는 외부화 가능 오브젝트와는 다른 방법으로 직렬화됩니다. 열거 형 정수의 직렬화 된 형식은, 이름만으로 구성됩니다. 상수의 필드 값이 양식에 없습니다. enum 정수를 직렬화하기 위해서 (때문에), ObjectOutputStream 는 enum 정수의 이름 메소드가 돌려주는 값을 ObjectOutputStream 합니다. enum 정수를 직렬화 복원하려면, ObjectInputStream 는 스트림으로부터 정수의 이름을 읽어들입니다. 비 직렬화 정수는, java.lang.Enum.valueOf 메소드를 호출 해, 인수의 정수로서 수신 한 정수의 이름과 함께 정수의 열거 형을 건네주는 것으로 취득 할 수 있습니다. 다른 직렬화 가능 객체 또는 외부화 가능 객체와 마찬가지로 열거 형 상수는 순차 화 스트림에 연속적으로 나타나는 역 참조의 대상으로 작동 할 수 있습니다.

열거 형 정수를 직렬화하는 프로세스는 커스터마이즈 할 수 없습니다. 열거 형에 의해 정의 된 클래스 고유의 writeObject , readObject , readObjectNoData , writeReplacereadResolve 메서드는 직렬화 및 직렬화 복원 중에는 무시됩니다. 마찬가지로 모든 serialPersistentFields 또는 serialVersionUID 필드 선언도 무시됩니다. 모든 enum 유형의 고정 serialVersionUID0L 입니다. 전송할 데이터 유형에 변화가 없으므로 직렬화 가능 필드 및 열거 형 데이터를 문서화하는 것은 불필요합니다.

오라클 문서에서 인용

재래식 싱글 톤의 또 다른 문제점은 일단 Serializable 인터페이스를 구현하면 readObject() 메소드가 Java에서 생성자와 같은 새로운 인스턴스를 항상 리턴하기 때문에 더 이상 Singleton으로 남아 있지 않다는 것입니다. 이것은 readResolve() 하고 아래처럼 싱글 톤으로 대체하여 새로 생성 된 인스턴스를 버림으로써 피할 수 있습니다

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

싱글 톤 클래스가 상태를 유지하는 경우 일시적으로 처리해야하므로이 작업은 더욱 복잡해질 수 있지만 Enum Singleton에서는 JVM에서 직렬화를 보장합니다.

좋은 읽기

  1. 싱글 톤 패턴
  2. 열거 형, 싱글 톤 및 비 직렬화
  3. 더블 체크 잠금 및 싱글 톤 패턴



이 게임에 조금 늦을 수도 있지만 싱글 톤을 구현하는 데는 많은 뉘앙스가 있습니다. 홀더 패턴은 많은 경우 사용할 수 없습니다. 그리고 휘발성을 사용할 때 IMO - 당신은 또한 지역 변수를 사용해야합니다. 처음부터 시작하여 문제에 대해 반복 해 봅시다. 내가 무슨 뜻인지 알게 될거야.

첫 번째 시도는 다음과 같이 보일 수 있습니다.

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

여기서 우리는 INSTANCE라는 private static 멤버와 getInstance ()라는 public static 메서드를 가진 MySingleton 클래스를가집니다. 처음 getInstance ()가 호출되면 INSTANCE 멤버는 null입니다. 그런 다음 흐름이 생성 조건에 들어가서 MySingleton 클래스의 새 인스턴스를 만듭니다. 이후에 getInstance ()를 호출하면 INSTANCE 변수가 이미 설정되어 있으므로 다른 MySingleton 인스턴스가 만들어지지 않습니다. 이렇게하면 getInstance ()의 모든 호출자간에 공유되는 MySingleton 인스턴스가 하나만 존재하게됩니다.

그러나이 구현에는 문제가 있습니다. 다중 스레드 응용 프로그램은 단일 인스턴스 생성시 경쟁 조건을 갖습니다. 실행 스레드가 여러 개있는 경우 getInstance () 메서드가 동시에 (또는 그 둘레)에서 실행되면 INSTANCE 멤버가 null로 표시됩니다. 그러면 각 스레드가 새 MySingleton 인스턴스를 만들고 이후에 INSTANCE 멤버를 설정하게됩니다.

private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

여기에서는 메소드 서명에서 synchronized 키워드를 사용하여 getInstance () 메소드를 동기화했습니다. 이것은 우리의 경쟁 조건을 확실히 고칠 것입니다. 쓰레드는 이제 블록하고 한 번에 하나씩 메소드에 들어갑니다. 그러나 또한 성능 문제가 발생합니다. 이 구현은 단일 인스턴스의 생성을 동기화 할뿐만 아니라 읽기를 포함하여 getInstance ()에 대한 모든 호출을 동기화합니다. 읽기는 단순히 INSTANCE 값을 반환하기 때문에 동기화 할 필요가 없습니다. 읽기는 대부분의 호출을 구성하기 때문에 (인스턴스화는 첫 번째 호출에서만 발생 함) 전체 메서드를 동기화하여 불필요한 성능이 저하됩니다.

private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

여기서는 메서드 시그니처에서 MySingleton 인스턴스의 생성을 래핑하는 동기화 된 블록으로 동기화를 이동했습니다. 그러나 이것이 우리의 문제를 해결합니까? 글쎄, 우리는 더 이상 독서를 막지 않지만 우리는 한 발 뒤로 물러났습니다. 복수의 thread가 getInstance () 메소드를 동시에 또는 그 근처에서 히트합니다. 이것들은 모두 INSTANCE 멤버를 null로서 참조합니다. 그런 다음 동기화 된 블록에 도달하여 잠금을 얻고 인스턴스를 만듭니다. 해당 스레드가 블록을 종료하면 다른 스레드는 잠금을 위해 경합하고 각 스레드는 블록을 통과하여 클래스의 새 인스턴스를 만듭니다. 그래서 우리는 바로 우리가 시작했던 곳입니다.

private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

여기서 우리는 블록 안쪽에서 또 다른 체크를합니다. INSTANCE 멤버가 이미 설정되어 있으면 초기화를 건너 뜁니다. 이를 이중 확인 잠금이라고합니다.

이것은 다중 인스턴스화 문제를 해결합니다. 그러나 다시 한 번 우리의 솔루션은 또 다른 도전 과제를 제시했습니다. 다른 스레드는 INSTANCE 구성원이 갱신되었음을 "보지"못할 수도 있습니다. 이것은 Java가 메모리 조작을 최적화하는 방법 때문입니다. 스레드는 변수의 원래 값을 주 메모리에서 CPU의 캐시로 복사합니다. 그러면 값에 대한 변경 사항이 해당 캐시에 쓰여지고 캐시에서 읽습니다. 이것은 성능을 최적화하도록 설계된 Java의 기능입니다. 그러나 이것이 우리 싱글 톤 구현에 문제를 만듭니다. 다른 캐시를 사용하는 다른 CPU 또는 코어에 의해 처리되는 두 번째 스레드는 첫 번째 스레드에 의한 변경 사항을 보지 못합니다. 이것은 두 번째 쓰레드가 INSTANCE 멤버를 null로 보게 할 것이고, 우리 싱글 톤의 새로운 인스턴스가 생성되도록 만들 것이다.

private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

우리는 INSTANCE 멤버의 선언에 volatile 키워드를 사용하여이 문제를 해결합니다. 이렇게하면 컴파일러가 항상 CPU 캐시가 아닌 주 메모리에서 읽고 쓰도록 지시합니다.

그러나이 간단한 변화는 비용으로 발생합니다. 우리가 CPU 캐시를 우회하고 있기 때문에, 우리는 휘발성 인스턴스 멤버 (우리가 4 번 할 때마다)에서 작업 할 때마다 성능이 저하됩니다. 우리는 존재 (1과 2)를 다시 확인하고 값 (3)을 설정 한 다음 값 (4)을 반환합니다. 메서드의 첫 번째 호출 중에 인스턴스 만 만들면이 경로가 프린지 케이스라고 주장 할 수 있습니다. 아마도 제작에 대한 성과는 용인 될 수 있습니다. 그러나 우리의 주된 유스 케이스는 휘발성 멤버에게 두 번 운영 될 것입니다. 한 번 존재를 확인하고 다시 그 가치를 반환합니다.

private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

퍼포먼스 히트는 휘발성 멤버에서 직접 작동하기 때문에 로컬 변수를 휘발성 값으로 설정하고 대신 로컬 변수를 사용하도록합시다. 이렇게하면 우리가 휘발성으로 운영하는 횟수가 줄어들어 잃어버린 성과를 되 찾을 수 있습니다. synchronized 블록에 들어갈 때 지역 변수를 다시 설정해야합니다. 이것은 우리가 자물쇠를 기다리는 동안 발생한 모든 변경 사항을 최신으로 유지합니다.

나는 이것에 관한 기사를 최근에 썼다. 싱글 톤을 해체하기 . 이 예들에 대한 더 많은 정보와 그곳에있는 "홀더"패턴의 예를 찾을 수 있습니다. 또한 이중 확인 휘발성 접근 방식을 보여주는 실제 사례가 있습니다. 희망이 도움이됩니다.




싱글 톤 객체를 만드는 다양한 방법들 :

  1. 조슈아 블로흐 (Joshua Bloch) - Enum이 최고 일 것입니다.

  2. 이중 잠금을 사용할 수도 있습니다.

  3. 내부 정적 클래스도 사용할 수 있습니다.




다음은 간단한 구현 방법입니다 singleton.

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){};
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

이렇게하면 게으르게 만들 수 있습니다 singleton:

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){};
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}



싱글 톤에 대해 자주 사용되는 또 다른 주장은 테스트 가능성 문제입니다. 싱글 톤은 테스트 목적으로 쉽게 모의되지 않습니다. 이것이 문제가된다면 다음과 같이 약간 수정하면됩니다.

public class SingletonImpl {

    private static SingletonImpl instance;

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

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

추가 된 setInstance메서드를 사용하면 테스트 중에 싱글 톤 클래스의 실물 크기 구현을 설정할 수 있습니다.

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

이것은 또한 초기 초기화 접근법과 함께 작동합니다.

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

이것은이 기능을 일반 응용 프로그램에도 노출시키는 단점이 있습니다. 이 코드로 작업하는 다른 개발자는 theetInstance 메서드를 사용하여 특정 함수를 변경하고 전체 응용 프로그램 동작을 변경하려는 유혹을받을 수 있습니다. 따라서이 메서드는 적어도 javadoc에 좋은 경고를 포함해야합니다.

여전히 모형 테스트 (필요시) 가능성 때문에이 코드 노출은 지불 할만한 적절한 가격이 될 수 있습니다.




나는 여전히 Java 1.5 이후에 enum이 멀티 스레드 환경에서도 보장된다는 점에서 사용 가능한 최상의 싱글 톤 구현이라고 생각합니다. 단 하나의 인스턴스 만 생성됩니다.

public enum Singleton{ INSTANCE; }

그리고 너 끝났어!




필자가 본 최고의 싱글 톤 패턴은 Supplier 인터페이스를 사용합니다.

  • 그것은 일반적이고 재사용 가능하다.
  • lazy 초기화를 지원합니다.
  • 초기화 될 때까지만 동기화되고 차단 공급자는 차단 공급자가 아닙니다.

아래 참조 :

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}



public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

getInstance 전에 Synchronized 키워드를 추가 했으므로 두 스레드가 동시에 getInstance를 호출하는 경우 경쟁 조건을 피할 수 있습니다.




Related