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

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



답변

열거 형을 사용하십시오.

public enum Foo {
    INSTANCE;
}

Joshua Bloch는 Google I / O 2008에서의 효과적인 Java Reloaded 강연에서 비디오에 대한 링크 에서이 접근 방식을 설명했습니다 . 그의 프레젠테이션의 슬라이드 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));
    }
}

편집 : “유효한 Java”온라인 부분 은 다음과 같이 말합니다.

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


답변

사용법에 따라 몇 가지 “올바른”답변이 있습니다.

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키워드를 사용하여 최종 사용자에게 알 렸습니다. 그런 다음 사용자가 자신의 Foo를 만들지 못하도록 생성자를 비공개로 만들어야합니다. 생성자에서 예외를 throw하면 사용자가 리플렉션을 사용하여 두 번째 Foo를 만들 수 없습니다. 그런 다음 private static final Foo유일한 인스턴스를 보유 할 필드와 public static Foo getInstance()이를 리턴 하는 메소드를 작성하십시오. Java 스펙은 생성자가 클래스를 처음 사용할 때만 호출되도록합니다.

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

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


답변

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


Singleton을 구현하는 동안 우리는 두 가지 옵션이 있습니다
. 1. Lazy loading
2. Early loading

게으른 로딩은 비트 오버 헤드 (많은 솔직함)를 추가하므로 매우 큰 객체 또는 무거운 구성 코드가 있고 인스턴스가 필요하기 전에 사용할 수있는 다른 액세스 가능한 정적 메소드 또는 필드가있는 경우에만 사용하십시오 초기 로딩을 선택하는 것이 좋습니다.

싱글 톤을 구현하는 가장 간단한 방법은

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

이제 우리는 사악한 스레드에 대해 확신하지만 잔인한 직렬화는 어떻습니까? de-serialization이 새 개체를 만들지 않아도 확인해야합니다.

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 이상에서 유효합니다. 그러나 오류가 발생하기 쉽다고 생각하기 때문에 사용하지 않는 것이 좋습니다.

변덕스러운 진술을 잊기 쉽고 그것이 왜 필요한지 이해하기 어렵다. 휘발성이 없으면이 코드는 이중 점검 잠금 반 패턴으로 인해 더 이상 스레드로부터 안전하지 않습니다. 이에 대한 자세한 내용은 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;
    }
}


답변

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 Memory Model)에서 다른 스레드의 값 변경을 보증하지 않으므로 중요합니다. 동기화 는이 를 처리 하지 않으며 해당 코드 블록에 대한 액세스 만 직렬화합니다.

편집 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가 테스트 목적으로 조롱 된 리소스를 사용할 수 있기 때문에 단위 테스트를 사용하는 경우 특히 종속성 주입이 더 유용하다는 것을 알았습니다.