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

StackOverflow https://stackoverflow.com/questions/70689

  •  09-06-2019
  •  | 
  •  

문제

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

도움이 되었습니까?

해결책

열거형을 사용하세요:

public enum Foo {
    INSTANCE;
}

Joshua Bloch는 그의 저서에서 이러한 접근 방식을 설명했습니다. 효과적인 Java 리로드됨 Google I/O 2008에서 연설: 비디오 링크.또한 그의 프리젠테이션 슬라이드 30-32를 참조하십시오(Effective_java_reloaded.pdf):

직렬화 가능한 싱글톤을 구현하는 올바른 방법

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

편집하다:"Effective Java"의 온라인 부분 말한다:

"이 접근 방식은 더 간결하고 직렬화 기계를 무료로 제공하며 정교한 직렬화 또는 반사 공격에도 불구하고 다중 인스턴스화에 대한 철통같은 보장을 제공한다는 점을 제외하면 공개 필드 접근 방식과 기능적으로 동일합니다.이 접근법은 아직 널리 채택되지는 않았지만, 단일 요소 열거형은 싱글톤을 구현하는 가장 좋은 방법입니다.."

다른 팁

사용법에 따라 여러 가지 "정답"이 있습니다.

java5를 수행하는 가장 좋은 방법은 열거형을 사용하는 것입니다.

public enum Foo {
   INSTANCE;
}

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 사용자에게 최종임을 알리는 키워드입니다.그런 다음 사용자가 자신만의 Foo를 생성하는 것을 방지하기 위해 생성자를 비공개로 만들어야 합니다.생성자에서 예외를 발생시키면 사용자가 리플렉션을 사용하여 두 번째 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 클래스가 실제로 사용될 때만 실행됩니다. 이는 지연 인스턴스화를 처리하며 스레드로부터 안전하다는 것을 보장합니다.

또한 개체를 직렬화할 수 있으려면 역직렬화가 복사본을 생성하지 않도록 해야 합니다.

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() 이전 프로그램 실행에서 개체가 직렬화되었더라도 유일한 인스턴스가 반환되도록 합니다.

부인 성명: 나는 방금 멋진 답변을 모두 요약하고 내 말로 썼습니다.


싱글톤을 구현하는 동안 두 가지 옵션이 있습니다.
1.지연 로딩
2.조기 로딩

지연 로딩은 약간의 오버헤드를 추가하므로(솔직히 말하면 많은) 매우 큰 개체나 무거운 구성 코드가 있고 인스턴스가 필요하기 전에 사용할 수 있는 다른 액세스 가능한 정적 메서드나 필드가 있는 경우에만 사용하십시오. 지연 초기화를 사용해야 합니다. 그렇지 않으면 초기 로딩을 선택하는 것이 좋은 선택입니다.

싱글톤을 구현하는 가장 간단한 방법은 다음과 같습니다.

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

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

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

초기에 로드된 싱글톤을 제외하면 모든 것이 좋습니다.게으른 로드 싱글톤을 시도해 보겠습니다.

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

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

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

지금까지는 훌륭했지만 우리 영웅은 우리 영웅의 인스턴스를 많이 원하는 여러 사악한 스레드와 혼자 싸우면서 살아남지 못할 것입니다.그러니 사악한 멀티 스레딩으로부터 보호하자

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

하지만 영웅을 보호하는 것만으로는 충분하지 않습니다. 정말!!!이것이 우리의 영웅을 돕기 위해 우리가 할 수 있고 해야 하는 최선입니다

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

이것을 "이중 검사 잠금 관용구"라고 합니다.변덕스러운 진술을 잊어버리기 쉽고 그것이 필요한 이유를 이해하기 어렵습니다.
자세한 내용은 다음과 같습니다. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

이제 우리는 사악한 실에 대해 확신하지만 잔인한 연재는 어떻습니까?역직렬화 중에도 새 개체가 생성되지 않도록 해야 합니다.

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

방법 readResolve() 이전 프로그램 실행에서 객체가 직렬화되었더라도 유일한 인스턴스가 반환되도록 할 것입니다.

마지막으로 스레드 및 직렬화에 대한 충분한 보호 기능을 추가했지만 코드가 너무 크고 보기 흉해 보입니다.영웅에게 변신을 시키자

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

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

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

네, 이것은 우리와 똑같은 영웅입니다 :)
라인 이후로 private static final Foo INSTANCE = new Foo(); 클래스가 있을 때만 실행됩니다. FooLoader 실제로 사용되는 경우 이는 게으른 인스턴스화를 처리합니다.

스레드 안전이 보장됩니까?

그리고 우리는 지금까지 왔습니다. 여기에 우리가 한 모든 일을 달성하는 가장 좋은 방법이 있습니다.

 public enum Foo {
       INSTANCE;
   }

내부적으로는 다음과 같이 처리됩니다.

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

이제 직렬화, 스레드, 보기 흉한 코드에 대한 두려움이 없습니다.또한 ENUMS 싱글톤이 느리게 초기화됩니다..

이 접근법은 더 간결하고 직렬화 기계를 무료로 제공하며 정교한 직렬화 또는 반사 공격에 직면하더라도 여러 인스턴스화에 대한 철제 보증을 제공한다는 점을 제외하고는 공공 필드 접근 방식과 기능적으로 동일합니다.이 접근법은 아직 널리 채택되지 않았지만 단일 요소 열거 유형이 싱글 톤을 구현하는 가장 좋은 방법입니다.

- 『이펙티브 자바』의 조슈아 블로흐

이제 ENUMS가 Singleton을 구현하는 가장 좋은 방법으로 간주되는 이유를 깨달았을 것입니다. 인내심을 가져주셔서 감사합니다 :)
내에서 업데이트했습니다. 블로그.

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

변덕스러운 진술을 잊어버리기 쉽고 그것이 필요한 이유를 이해하기 어렵습니다.휘발성이 없으면 이 코드는 이중 확인 잠금 안티패턴으로 인해 더 이상 스레드로부터 안전하지 않습니다.이에 대한 자세한 내용은 16.2.4절을 참조하세요. 실제 Java 동시성.간단히 말해서:이 패턴(Java5.0 이전 또는 휘발성 문 없음)은 (여전히) 잘못된 상태에 있는 Bar 객체에 대한 참조를 반환할 수 있습니다.

이 패턴은 성능 최적화를 위해 고안되었습니다.그러나 이것은 더 이상 진정한 관심사가 아닙니다.다음 지연 초기화 코드는 빠르고 -더 중요한 것은- 읽기 쉽습니다.

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

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

Java 5+에서 스레드로부터 안전함:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

편집하다:다음에 주의하세요. volatile 여기 수정자.:) 이것이 없으면 다른 스레드가 JMM(Java 메모리 모델)에서 해당 값의 변경 사항을 볼 수 있도록 보장되지 않기 때문에 중요합니다.동기화 하지 않습니다 주의하세요. 해당 코드 블록에 대한 액세스만 직렬화합니다.

편집 2:@Bno의 답변은 Bill Pugh(FindBugs)가 권장하는 접근 방식을 자세히 설명하며 더 나은 논쟁의 여지가 있습니다.가서 그의 답변도 읽고 투표하세요.

잊다 지연 초기화, 너무 문제가 많습니다.이것이 가장 간단한 해결책입니다:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

정말로 필요한지 확인하십시오.이에 대한 몇 가지 주장을 보려면 "싱글톤 안티 패턴"에 대한 Google을 검색해 보세요.내 생각에 본질적으로 잘못된 것은 없지만 일부 전역 리소스/데이터를 노출하기 위한 메커니즘일 뿐이므로 이것이 최선의 방법인지 확인하세요.특히 DI를 사용하면 테스트 목적으로 모의 리소스를 사용할 수 있으므로 단위 테스트도 사용하는 경우 종속성 주입이 더 유용하다는 것을 알았습니다.

싱글톤 사용의 대안으로 DI를 제안하는 일부 답변이 어리둥절합니다.이것들은 관련없는 개념입니다.DI를 사용하여 싱글톤 또는 비싱글톤을 주입할 수 있습니다(예:스레드별) 인스턴스.적어도 Spring 2.x를 사용한다면 이는 사실입니다. 다른 DI 프레임워크에 대해서는 말할 수 없습니다.

따라서 OP에 대한 나의 대답은 (가장 사소한 샘플 코드를 제외하고) 다음과 같습니다.

  1. Spring과 같은 DI 프레임워크를 사용한 다음
  2. 종속성이 싱글톤인지, 요청 범위인지, 세션 범위인지에 관계없이 이를 DI 구성의 일부로 만드세요.

이 접근 방식은 싱글톤 사용 여부가 쉽게 되돌릴 수 있는 구현 세부 사항인 멋진 분리된(따라서 유연하고 테스트 가능한) 아키텍처를 제공합니다(물론 사용하는 모든 싱글톤이 스레드로부터 안전한 경우).

싱글톤을 작성하기 전에 왜 싱글톤이 필요한지 생각해 보세요.Java에서 싱글톤을 검색하면 쉽게 넘어갈 수 있는 준종교적 논쟁이 있습니다.

개인적으로 나는 여러 가지 이유로 가능한 한 자주 싱글톤을 피하려고 노력하는데, 그 중 대부분은 구글링 싱글톤을 통해 찾을 수 있습니다.나는 싱글톤이 모든 사람이 이해하기 쉽고 "전역" 데이터를 OO 디자인으로 가져오는 메커니즘으로 사용되며 객체 수명주기 관리(또는 객체 수명주기 관리(또는 B 내부에서 A를 어떻게 할 수 있는지 정말로 생각하고 있습니다.)좋은 중간 지점을 찾으려면 제어 반전(IoC) 또는 종속성 주입(DI)과 같은 것을 살펴보세요.

정말로 필요하다면 위키피디아에 싱글톤의 적절한 구현에 대한 좋은 예가 있습니다.

다음은 3가지 접근 방식입니다.

1) 열거형

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) 이중 확인 잠금/지연 로딩

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3) 정적 팩토리 메소드

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

저는 Spring Framework를 사용하여 싱글톤을 관리합니다.이는 클래스의 "싱글톤성"(여러 클래스 로더가 관련된 경우 실제로 수행할 수 없음)을 강제하지 않지만 다양한 유형의 객체를 생성하기 위해 다양한 팩토리를 구축하고 구성하는 정말 쉬운 방법을 제공합니다.

버전 1:

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

지연 로딩, 차단을 통한 스레드 안전, 낮은 성능 synchronized.

버전 2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

지연 로딩, 스레드 안전, 비차단, 고성능.

위키피디아에는 일부가 있습니다. 싱글톤(Java에서도 마찬가지)Java 5 구현은 매우 완벽해 보이며 스레드로부터 안전합니다(이중 확인 잠금 적용).

지연 로딩이 필요하지 않다면 간단히 시도해보십시오.

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

지연 로딩을 원하고 싱글톤을 스레드로부터 안전하게 만들고 싶다면 이중 확인 패턴을 사용해 보세요.

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

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

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

이중 검사 패턴의 작동이 보장되지 않으므로(컴파일러 관련 문제로 인해 이에 대해 더 이상 아는 바가 없습니다.) 전체 getInstance 메서드를 동기화하거나 모든 싱글톤에 대한 레지스트리를 생성할 수도 있습니다.

나는 Enum 싱글턴이라고 말하고 싶습니다.

Java에서 enum을 사용하는 싱글톤은 일반적으로 enum 싱글톤을 선언하는 방법입니다.Enum 싱글톤에는 인스턴스 변수와 인스턴스 메서드가 포함될 수 있습니다.단순화를 위해 인스턴스 메서드를 사용하는 경우 개체 상태에 영향을 미치는 경우 해당 메서드의 스레드 안전성을 보장해야 한다는 점도 참고하세요.

열거형을 사용하는 것은 구현하기가 매우 쉽고 다른 방법으로 우회해야 하는 직렬화 가능 개체와 관련된 단점이 없습니다.

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

다음을 통해 액세스할 수 있습니다. Singleton.INSTANCE, 전화하는 것보다 훨씬 쉽습니다 getInstance() 싱글턴의 메소드.

1.12 열거형 상수의 직렬화

열거형 상수는 일반적인 직렬화 가능 또는 외부화 가능 객체와 다르게 직렬화됩니다.열거형 상수의 직렬화된 형식은 해당 이름만으로 구성됩니다.상수의 필드 값이 양식에 존재하지 않습니다.열거형 상수를 직렬화하려면, ObjectOutputStream 열거형 상수의 name 메서드에서 반환된 값을 씁니다.열거형 상수를 역직렬화하려면, ObjectInputStream 스트림에서 상수 이름을 읽습니다.역직렬화된 상수는 다음을 호출하여 얻습니다. java.lang.Enum.valueOf 메소드, 수신된 상수 이름과 함께 상수의 열거형 유형을 인수로 전달합니다.다른 직렬화 가능 또는 외부화 가능 객체와 마찬가지로 열거형 상수는 직렬화 스트림에서 이후에 나타나는 역참조의 대상으로 작동할 수 있습니다.

열거형 상수가 직렬화되는 프로세스는 사용자 정의할 수 없습니다.모든 클래스별 writeObject, readObject, readObjectNoData, writeReplace, 그리고 readResolve 열거형 유형으로 정의된 메서드는 직렬화 및 역직렬화 중에 무시됩니다.마찬가지로, 어떤 serialPersistentFields 또는 serialVersionUID 필드 선언도 무시됩니다. 모든 열거형 유형은 고정되어 있습니다. serialVersionUID ~의 0L.전송되는 데이터 유형에 변화가 없기 때문에 열거형 유형에 대한 직렬화 가능 필드와 데이터를 문서화하는 것은 불필요합니다.

Oracle 문서에서 인용

기존 싱글톤의 또 다른 문제점은 일단 구현하면 Serializable 인터페이스에서는 더 이상 싱글톤으로 유지되지 않습니다. readObject() 메소드는 항상 Java의 생성자와 같은 새 인스턴스를 반환합니다.이는 다음을 사용하여 피할 수 있습니다. readResolve() 아래와 같이 싱글톤으로 교체하여 새로 생성된 인스턴스를 폐기합니다.

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

Singleton 클래스가 상태를 유지하는 경우 일시적으로 만들어야 하기 때문에 이는 훨씬 더 복잡해질 수 있지만 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라는 전용 정적 멤버와 getInstance()라는 공용 정적 메서드가 있는 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;
}

여기서는 getInstance() 메서드를 동기화하기 위해 메서드 시그니처의 동기화 키워드를 사용했습니다.이것은 우리의 경쟁 상황을 확실히 해결해 줄 것입니다.이제 스레드는 한 번에 하나씩 메서드를 차단하고 입력합니다.하지만 성능 문제도 발생합니다.이 구현은 단일 인스턴스 생성을 동기화할 뿐만 아니라 읽기를 포함하여 getInstance()에 대한 모든 호출을 동기화합니다.읽기는 단순히 INSTANCE 값을 반환하므로 동기화할 필요가 없습니다.읽기는 호출의 대부분을 차지하므로(인스턴스화는 첫 번째 호출에서만 발생함을 기억하세요) 전체 메서드를 동기화하면 불필요한 성능 저하가 발생합니다.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

여기에서는 메서드 서명에서 MySingleton 인스턴스 생성을 래핑하는 동기화된 블록으로 동기화를 이동했습니다.하지만 이것이 우리의 문제를 해결합니까?글쎄, 우리는 더 이상 읽기를 차단하지 않지만 한 단계 뒤로 물러났습니다.여러 스레드가 동시에 또는 거의 동시에 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 멤버 선언에 휘발성 키워드를 사용하여 이 문제를 해결합니다.이는 컴파일러에게 항상 CPU 캐시가 아닌 메인 메모리에서 읽고 쓰도록 지시합니다.

하지만 이 단순한 변화에는 대가가 따른다.CPU 캐시를 우회하기 때문에 휘발성 INSTANCE 멤버에서 작업을 수행할 때마다 성능 저하가 발생합니다. 이 작업을 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;
}

성능 저하는 휘발성 멤버에 대한 직접 작업으로 인해 발생하므로 로컬 변수를 휘발성 값으로 설정하고 대신 로컬 변수에 작업해 보겠습니다.이렇게 하면 휘발성에 대한 작업 횟수가 줄어들어 손실된 성능을 일부 회복할 수 있습니다.동기화된 블록에 들어갈 때 로컬 변수를 다시 설정해야 한다는 점에 유의하세요.이렇게 하면 잠금을 기다리는 동안 발생한 모든 변경 사항이 최신 상태로 유지됩니다.

최근에 이에 관한 기사를 썼습니다. 싱글톤 해체.여기에서 이러한 예제와 "홀더" 패턴의 예제에 대한 자세한 정보를 찾을 수 있습니다.이중 확인된 휘발성 접근 방식을 보여주는 실제 사례도 있습니다.도움이 되었기를 바랍니다.

There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    }

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    }


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

간단한 구현 방법은 다음과 같습니다. 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();
    }
}

당신은 필요 다시 확인 중 클래스의 인스턴스 변수를 느리게 로드해야 하는 경우의 관용구입니다.정적 변수나 싱글톤을 느리게 로드해야 하는 경우 다음이 필요합니다. 요청 시 초기화 홀더 관용구.

또한, 싱글톤이 직렬화 가능해야 하는 경우 다른 모든 필드는 일시적이어야 하며 싱글톤 개체 불변성을 유지하려면 readResolve() 메서드를 구현해야 합니다.그렇지 않으면 개체가 역직렬화될 때마다 개체의 새 인스턴스가 생성됩니다.readResolve()가 하는 일은 readObject()가 읽은 새 객체를 대체하는 것인데, 이를 참조하는 변수가 없기 때문에 새 객체를 가비지 수집하게 됩니다.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 

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

  1. Joshua Bloch에 따르면 Enum이 최고일 것입니다.

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

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

열거형 싱글톤

스레드로부터 안전한 싱글톤을 구현하는 가장 간단한 방법은 Enum을 사용하는 것입니다.

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

이 코드는 Java 1.5에 Enum이 도입된 이후 작동합니다.

이중 확인 잠금

다중 스레드 환경(Java 1.5부터 시작)에서 작동하는 "클래식" 싱글톤을 코딩하려면 이 것을 사용해야 합니다.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

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

휘발성 키워드의 구현이 다르기 때문에 1.5 이전에는 스레드로부터 안전하지 않습니다.

싱글톤 조기 로딩(Java 1.5 이전에도 작동함)

이 구현은 클래스가 로드될 때 싱글톤을 인스턴스화하고 스레드 안전성을 제공합니다.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}

JSE 5.0 이상의 경우 Enum 접근 방식을 사용하고, 그렇지 않으면 정적 싱글톤 홀더 접근 방식(Bill Pugh가 설명한 지연 로딩 접근 방식)을 사용합니다.후자의 솔루션은 특별한 언어 구성(예:휘발성 또는 동기화됨).

싱글톤에 대해 자주 사용되는 또 다른 주장은 테스트 가능성 문제입니다.싱글톤은 테스트 목적으로 쉽게 조롱할 수 없습니다.이것이 문제로 판명되면 다음과 같이 약간 수정하고 싶습니다.

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");
    }

}

이는 이 기능을 일반 애플리케이션에도 노출시키는 단점이 있습니다.해당 코드를 작업하는 다른 개발자는 'setInstance' 메소드를 사용하여 특정 기능을 변경하고 전체 애플리케이션 동작을 변경하려는 유혹을 받을 수 있으므로 이 메소드에는 javadoc에 최소한 좋은 경고가 포함되어야 합니다.

그럼에도 불구하고 (필요한 경우) 모형 테스트 가능성을 고려하면 이 코드 노출은 지불할 수 있는 대가일 수 있습니다.

가장 간단한 싱글턴 클래스

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}

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

public enum Singleton{ INSTANCE; }

그리고 당신은 끝났습니다 !!!

이 게시물을 살펴보십시오.

Java 핵심 라이브러리의 GoF 디자인 패턴 예

베스트 답변의 "Singleton" 섹션에서,

싱글톤(매번 동일한 인스턴스(일반적으로 자체)를 반환하는 생성 메서드로 인식 가능)

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()

Java 기본 클래스 자체에서 Singleton의 예를 배울 수도 있습니다.

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

  • 일반적이고 재사용이 가능합니다.
  • 지연 초기화를 지원합니다.
  • 초기화될 때까지만 동기화된 다음 차단 공급자가 비차단 공급자로 대체됩니다.

아래를 참조하세요:

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();
    }
}

때로는 간단한 "static Foo foo = new Foo();" 충분하지 않다.수행하려는 몇 가지 기본 데이터 삽입을 생각해 보십시오.

반면에 싱글톤 변수를 인스턴스화하는 모든 메서드를 동기화해야 합니다.동기화는 그 자체로 나쁘지는 않지만 성능 문제나 잠금으로 이어질 수 있습니다(이 예를 사용하는 경우는 매우 드물지만).해결책은

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

이제 어떻게 되나요?클래스는 클래스 로더를 통해 로드됩니다.클래스가 바이트 배열에서 해석된 직후 VM은 다음을 실행합니다. 정적 { } - 차단하다.그게 전부 비밀이에요:정적 블록은 주어진 패키지의 주어진 클래스(이름)가 이 클래스 로더에 의해 로드될 때 한 번만 호출됩니다.

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 앞에 동기화 키워드를 추가했기 때문에 두 스레드가 동시에 getInstance를 호출하는 경우의 경쟁 조건을 방지했습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top