it-swarm-ko.tech

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

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

765

열거 형을 사용하십시오.

public enum Foo {
    INSTANCE;
}

Joshua Bloch는이 접근 방식을 Effective Java Reloaded 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"의 온라인 부분 말한다 :

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

768
Stephen Denne

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

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

public enum Foo {
   INSTANCE;
}

Pre Java 5의 가장 간단한 경우는 다음과 같습니다.

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를 만들지 못하도록 생성자를 비공개로 만들 필요가 있습니다. 생성자에서 예외를 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() 메서드는 이전에 프로그램이 실행될 때 개체가 serialize 된 경우에도 유일한 인스턴스가 반환되도록합니다.

227
Roel Spilker

면책 조항 : 나는 모든 멋진 답변을 요약하고 내 말로 썼습니다.


싱글 톤을 구현하는 동안 우리는 2 가지 옵션을가집니다.
1. 게으른 로딩
2. 조기 하중

게으른 로딩은 비트 오버 헤드를 추가하므로 매우 큰 객체 나 무거운 코드가 있고 인스턴스가 필요하기 전에 사용할 수있는 다른 액세스 가능한 정적 메서드 나 필드가있는 경우에만 사용하십시오 (단, 정직해야 함). 당신은 게으른 초기화를 사용해야합니다. 그렇지 않으면 초기로드를 선택하는 것이 좋습니다.

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

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

이것은 "Double-Checked Locking Idiom"이라고합니다. 휘발성 선언을 잊어 버리고 필요한 이유를 이해하기 어렵습니다.
자세한 내용은 : 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 클래스가 실제로 사용될 때만 실행되기 때문에, 이것은 게으른 인스턴스 생성,

thread 세이프 인 것을 보증합니다.

그리고 우리는 지금까지 왔으며, 우리가 한 모든 것을 성취하는 가장 좋은 방법은 최선의 방법입니다.

 public enum Foo {
       INSTANCE;
   }

내부적으로 어떤 대우를받을 것인가?

public class Foo {

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

직렬화, 스레드 및 추한 코드에 대한 두려움이 없습니다. 또한 ENUMS 싱글 톤은 느리게 초기화된다. .

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

- "효과적인 자바"의 Joshua Bloch

이제 왜 ENUMS가 Singleton을 구현하고 인내심을 가져 주셔서 감사합니다.
blog 에서 업데이트했습니다.

130
xyz

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;
    }
}
123
Benno Richters

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

EDIT : 여기서 volatile 수정 자에주의하십시오. :) 그렇지 않으면 JMM (Java Memory Model)에서 다른 스레드의 값 변경을 보증하지 않으므로 중요합니다. 동기화 는이를 처리하지 않으며 해당 코드 블록에 대한 액세스 만 직렬화합니다.

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

94
Stu Thompson

잊어 버린 지연 초기화 , 너무 문제가있어. 이것은 가장 간단한 솔루션입니다.

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

당신이 정말로 그것을 필요로하는지 확인하십시오. 그것에 대해 몇 가지 주장을 보려면 "싱글 톤 안티 패턴 (singleton anti-pattern)"에 대해 Google을 수행하십시오. 본질적으로 잘못된 것은 없지만 일부 글로벌 리소스/데이터를 노출하기위한 메커니즘이므로 이것이 최선의 방법인지 확인하십시오. 특히 DI는 테스트 목적으로 조롱 된 리소스를 사용할 수 있기 때문에 단위 테스트를 사용하는 경우 종속성 주입이 더욱 유용하다는 것을 알았습니다.

47
Neil Burroughs

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

26
Javamann

싱글 톤을 사용하는 것에 대한 대안으로 DI를 제안하는 몇 가지 대답에 의해 나는 신비 스럽다. 이들은 무관 한 개념이다. DI를 사용하여 싱글 톤 또는 비 - 싱글 톤 (예 : 스레드 당) 인스턴스를 주입 할 수 있습니다. Spring 2.x를 사용한다면 적어도 다른 DI 프레임 워크는 말할 수 없습니다.

그래서 OP에 대한 내 대답은 (모든 사소한 샘플 코드에서) 다음과 같습니다.

  1. Spring과 같은 DI 프레임 워크를 사용하십시오.
  2. 의존성이 싱글 톤 (singleton), 범위 요청 (scope scoped), 세션 범위 (session scoped) 등 무엇이든 DI 구성에 포함 시키십시오.

이 접근 방식은 싱글 톤을 사용할지 여부는 쉽게 되돌릴 수있는 구현 세부 사항 (사용하는 싱글 톤이 물론 스레드 안전)을 제공하는 Nice 디커플링 (따라서 유연하고 테스트 가능한) 아키텍처를 제공합니다.

21
Andrew Swan

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

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

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

20
Aidos

다음 세 가지 접근법이 있습니다.

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;
    }
}
16
Abhijit Gaikwad

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

13
Matt

버전 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;
    }
}

게으른 로딩, 비 차단 및 고성능 스레드로 안전합니다.

11
coderz

Wikipedia는 자바에서도 싱글 톤의 examples 를 가지고 있습니다. Java 5 구현은 꽤 완벽 해 보이며 thread-safe (이중 선택 잠금이 적용됨)입니다.

10
macbirdie

게으른로드가 필요하지 않으면 간단히 시도하십시오.

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-method를 동기화하거나 모든 Singleton에 대한 레지스트리를 만들려고 할 수도 있습니다.

10
Aleksi Yrttiaho

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


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

public class MySingleton {

     private static MySingleton INSTANCE;

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

        return INSTANCE;
    }
    ...
}

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

그러나이 구현에는 문제가 있습니다. 다중 스레드 응용 프로그램은 단일 인스턴스 생성시 경쟁 조건을 갖습니다. 실행의 복수의 thread가 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 블록을 입력 할 때 지역 변수를 다시 설정해야합니다. 이것은 우리가 자물쇠를 기다리는 동안 발생한 모든 변경 사항을 최신 상태로 유지합니다.

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

8
Michael Andrews

나는 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에서 getInstance() 메서드를 호출하는 것보다 훨씬 쉽게 Singleton.INSTANCE를 사용하여 액세스 할 수 있습니다.

1.12 열거 형 상수의 직렬화

Enum 정수는, 통상의 직렬화 가능 오브젝트 또는 외부화 가능 오브젝트와는 다른 방법으로 직렬화됩니다. 열거 형 정수의 직렬화 된 형식은, 이름만으로 구성됩니다. 상수의 필드 값은 양식에 없습니다. enum 상수를 serialize하기 위해 ObjectOutputStream은 enum 상수의 name 메서드에서 반환 한 값을 씁니다. enum 상수를 deserialize하려면 ObjectInputStream이 상수 이름을 스트림에서 읽습니다. deserialized 상수는 Java.lang.Enum.valueOf 메서드를 호출하여 상수의 enum 형식과 인수로받은 상수 이름을 전달하여 얻을 수 있습니다. 다른 직렬화 가능 객체 또는 외부화 가능 객체와 마찬가지로 열거 형 상수는 순차 화 스트림에 연속적으로 나타나는 역 참조의 대상으로도 작동 할 수 있습니다.

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

오라클 워드 프로세서에서 인용

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

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

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


좋은 읽기

  1. 싱글 톤 패턴
  2. 열거 형, 싱글 톤 및 비 직렬화
  3. 이중 검사 잠금 및 단일 패턴
8
NullPoiиteя
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");
    }
}
7
Dheeraj Sachan

이것은 간단한 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();
    }
}
4
Nicolas Filotto

Lazily 클래스의 인스턴스 변수를로드해야 할 경우 double-checking idiom이 필요합니다. 정적 변수 나 싱글 톤 지연을로드해야하는 경우 initilization on demand holder idiom이 필요합니다.

또한, 싱글 톤이 연속 될 필요가있는 경우, 다른 모든 필드는 일시적 일 필요가 있고 싱글 톤 객체를 불변으로 유지하기 위해 readResolve () 메소드를 구현해야합니다. 그렇지 않으면 개체가 deserialize 될 때마다 개체의 새 인스턴스가 만들어집니다. readResolve ()가 수행하는 작업은 readObject ()에 의해 읽혀진 새로운 객체를 대체하는 것으로,이 객체를 참조하는 변수가 없으므로 새 객체를 가비지 수집해야했습니다.

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

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

  1. Joshua Bloch 당 - Enum이 최고 일 것입니다.

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

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

3
Shailendra Singh

싱글 톤 열거 형

Thread-safe 인 Singleton을 구현하는 가장 간단한 방법은 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");
  }

}
3
Dan Moldovan

JSE 5.0 이상에서는 Enum 접근법을 사용하고 그렇지 않으면 정적 싱글 톤 홀더 접근법 (Bill Pugh가 설명하는 지연로드 방식)을 사용하십시오. 후기 솔루션은 특수 언어 구조 (예 : 휘발성 또는 동기화 됨)를 요구하지 않고 스레드로부터 안전합니다.

2
raoadnan

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

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에 좋은 경고를 포함해야합니다.

여전히 모형 테스트 (필요할 경우) 가능성 때문에이 코드 노출은 지불 할 수있는 수용 가능한 가격 일 수 있습니다.

2
user3792852

가장 단순한 싱글 톤 클래스

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

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

public enum Singleton{ INSTANCE; }

그리고 너 끝났어!

0
shikjohari

이 게시물을보십시오.

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

가장 좋은 답변의 "Singleton"섹션에서,

싱글 톤 (매번 같은 인스턴스를 반환하는 생성 메소드로 인식 가능)

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

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

0
itiskj

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

  • 그것은 일반적이고 재사용 가능하다.
  • 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();
    }
}
0
user1024314