IT이야기

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

cyworld 2022. 6. 10. 21:31
반응형

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

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

열거형 사용:

public enum Foo {
    INSTANCE;
}

Joshua Bloch는 Google I/O 2008에서의 효과적인 Java 새로고침 토크: link to video에서 이 접근방식을 설명했습니다.프레젠테이션 슬라이드 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"의 온라인 부분에는 다음과 같은 내용이 있습니다.

「이 어프로치는, 보다 간결하고, 시리얼라이제이션 머신을 무료로 제공하고, 고도의 시리얼라이제이션이나 리플렉션 공격에 직면했을 경우에서도, 복수의 인스턴스화에 대한 확실한 보증을 제공하는 것을 제외하고, 기능적으로는 퍼블릭 필드 어프로치와 동등합니다.이 접근방식은 아직 널리 채택되지 않았지만 단일 요소 열거형이 싱글톤을 구현하는 가장 좋은 방법입니다."

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

Java 5 이후로는 enum을 사용하는 것이 가장 좋습니다.

public enum Foo {
   INSTANCE;
}

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

암호를 살펴봅시다.먼저, 당신은 수업이 마무리되기를 원합니다. 저는 우우, 는는이 the 를 사용했습니다.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는 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()오브젝트가 프로그램의 이전 실행에서 시리얼화된 경우에도 유일한 인스턴스가 반환되도록 합니다.

면책사항:저는 방금 모든 멋진 답을 요약해서 제 말로 썼어요.


싱글톤을 실장할 때는, 다음의 2개의 옵션이 있습니다.

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

이를 "더블 체크 잠금 관용구"라고 합니다.변덕스러운 발언을 잊어버리기 쉽고 왜 그것이 필요한지 이해하기 어렵다.상세한 것에 대하여는,"더블 체크된 잠금이 파손되었습니다" 선언

이제 우리는 사악한 실타래에 대해 확신하지만, 잔인한 연쇄화는 어떻습니까?디시리얼라이제이션 중에도 새로운 오브젝트가 생성되지 않았는지 확인해야 합니다.

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // The 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 싱글톤은 느긋하게 초기화됩니다.

이 접근방식은 보다 간결하고 시리얼라이제이션머신을 무료로 제공하며 복잡한 시리얼라이제이션 또는 리플렉션 공격 시에도 여러 인스턴스화에 대한 확실한 보증을 제공한다는 점을 제외하면 퍼블릭필드 접근방식과 기능적으로 동등합니다.이 접근방식은 아직 널리 채택되지 않았지만 단일 요소 열거형이 싱글톤을 구현하는 가장 좋은 방법이다.

- "유효한 자바"의 Joshua Bloch

ENUMS가 싱글톤을 구현하는 가장 좋은 방법인 이유를 알게 되셨을 것입니다.인내해 주셔서 감사합니다.

블로그에 업데이트했어

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

변덕스러운 발언을 잊어버리기 쉽고 왜 그것이 필요한지 이해하기 어렵다.휘발성이 없으면 잠금 방지 장치가 이중으로 체크되어 있기 때문에 이 코드는 더 이상 스레드 세이프가 되지 않습니다.자세한 내용은 Java Concurrency in Practice 단락 16.2.4를 참조하십시오.요컨대:이 패턴(Java 5.0 이전 또는 volatile 문이 없는 경우)은 잘못된 상태의 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)에 의해 값의 변경을 확인할 수 없기 때문에 중요합니다.동기화는 이 문제를 처리하지 않고 해당 코드 블록에 대한 액세스만 직렬화합니다.

@Bno의 답변은 Bill Pugh(Find Bugs)가 추천한 접근방식을 상세하게 설명하고 있어 논쟁의 여지가 있다.가서 그의 답을 읽고 투표해.

초기화가 느리다는 것은 잊으십시오.문제가 너무 커.가장 간단한 해결책은 다음과 같습니다.

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

꼭 필요하시도록 하세요."singleton anti-pattern"을 검색하여 이에 대한 몇 가지 주장을 확인하십시오.

본질적으로 문제될 것은 없다고 생각합니다만, 글로벌 자원/데이터를 공개하기 위한 메카니즘일 뿐이므로, 이것이 최선의 방법인지 확인해 주세요.특히 DI를 사용하면 테스트 목적으로 모의 리소스를 사용할 수 있기 때문에 Dependency Injection(DI; 의존성 주입)이 특히 유용합니다.

싱글톤을 사용하는 대신 의존성 주입(DI)을 제안하는 답변에 당황했습니다.이러한 답변은 관련이 없는 개념입니다.DI를 사용하여 싱글톤 또는 비싱글톤(예: 스레드 단위) 인스턴스를 주입할 수 있습니다.적어도 Spring 2.x를 사용하면 다른 DI 프레임워크는 말할 수 없습니다.

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

  1. 다음으로 Spring Framework와 같은 DI 프레임워크를 사용합니다.
  2. 종속성이 싱글톤인지, 요청 범위 지정인지, 세션 범위 지정인지 여부에 관계없이 DI 설정의 일부로 만듭니다.

이 접근방식을 통해 싱글톤을 사용할지 여부를 쉽게 되돌릴 수 있는 디커플링(유연하고 테스트 가능한) 아키텍처를 얻을 수 있습니다(물론 사용하는 싱글톤이 스레드 세이프인 경우).

싱글톤을 쓰기 전에 왜 필요한지 정말 생각해 보세요.자바에서 싱글톤을 검색하면 쉽게 넘어갈 수 있는 반종교적 논쟁이 있다.

개인적으로, 저는 여러 가지 이유로 싱글톤을 최대한 피하려고 노력하는데, 그 대부분은 싱글톤을 검색하면 찾을 수 있습니다.싱글톤은 누구나 이해하기 쉽기 때문에 남용되는 경우가 꽤 많다고 생각합니다."글로벌" 데이터를 OO 설계로 변환하는 메커니즘으로 사용되며 객체 수명 주기 관리(또는 B 내부에서 A를 수행할 수 있는 방법을 실제로 고려)를 회피하기 쉽기 때문에 사용됩니다.제어 반전(IoC) 또는 의존성 주입(DI)과 같은 좋은 중간 지평을 보십시오.

만약 당신이 정말로 그것을 필요로 한다면 위키피디아는 싱글톤의 적절한 구현의 좋은 예를 가지고 있다.

다음은 3가지 접근법입니다.

  1. 열거형

     /**
     * Singleton pattern example using Java Enum
     */
     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 {
         // Initialized 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;
         }
     }
    

스프링 프레임워크를 사용하여 싱글톤을 관리합니다.

클래스의 「싱글톤니스」를 강제하는 것은 아닙니다(클래스 로더가 여러 개 있는 경우는 실제로 실행할 수 없습니다).다만, 다른 타입의 오브젝트를 작성하기 위해서, 다른 팩토리를 구축해 설정할 수 있습니다.

싱글톤의 구현에 관해 많은 뉘앙스가 있다.홀더 패턴은 많은 상황에서 사용할 수 없습니다.또한 휘발성을 사용할 경우 IMO - 로컬 변수도 사용해야 합니다.처음부터 시작해서 그 문제를 반복해 봅시다.무슨 말인지 아시겠죠?


첫 번째 시도는 다음과 같습니다.

public class MySingleton {

     private static MySingleton INSTANCE;

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

여기에서는 INSTAST라는 이름의 프라이빗 스태틱멤버와 getInstance()라는 퍼블릭 스태틱메서드를 가진 MySingleton 클래스가 있습니다.getInstance()가 처음 호출되면 INstance 멤버는 늘이 됩니다.플로우는 작성 조건에 들어가 MySingleton 클래스의 새 인스턴스를 만듭니다.getInstance()에 대한 후속 호출에서는 INstance 변수가 이미 설정되어 있음을 발견하므로 다른 MySingleton 인스턴스는 생성되지 않습니다.이를 통해 getInstance()의 모든 발신자 간에 공유되는 MySingleton 인스턴스는 1개뿐입니다.

그러나 이 구현에는 문제가 있습니다.멀티 스레드 애플리케이션은 단일 인스턴스를 생성할 때 레이스 조건을 갖습니다.여러 개의 실행 스레드가 동시에(또는 그 주변에서) getInstance() 메서드를 히트하면 각각 INTERXT 멤버는 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 인스턴스 생성을 랩하는 동기화된 블록으로 이동했습니다.하지만 이것으로 문제가 해결되나요?더 이상 읽기를 차단하지 않지만 한 걸음 뒤로 물러났습니다.여러 스레드가 동시에 또는 그 부근에 getInstance() 메서드를 히트하여 모두 INTERNST 멤버를 늘로 인식합니다.

그런 다음 동기화된 블록에 도달하여 잠금을 취득하고 인스턴스를 만듭니다.이 스레드가 블록을 빠져나가면 다른 스레드가 잠금을 위해 경쟁하고 각 스레드가 하나씩 블록을 통과하여 새로운 클래스 인스턴스를 만듭니다.그래서 우리는 우리가 시작했던 곳으로 돌아왔어.


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 멤버를 늘로 인식하여 싱글톤의 새 인스턴스를 강제로 만듭니다.


private static volatile MySingleton INSTANCE;

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

문제를 해결하려면 인스턴스 멤버 선언에 volatile 키워드를 사용합니다.이를 통해 컴파일러는 CPU 캐시가 아닌 메인 메모리에서 항상 읽고 쓰도록 지시합니다.

그러나 이러한 단순한 변경에는 비용이 수반됩니다.CPU 캐시를 바이패스하고 있기 때문에 휘발성 인스턴스 멤버를 조작할 때마다 퍼포먼스에 영향을 줍니다(4회).존재 여부(1과 2)를 재확인하고 값(3)을 설정한 다음 값(4)을 반환합니다.메서드의 첫 번째 호출 시에만 인스턴스를 생성하기 때문에 이 경로가 프린지 케이스라고 주장할 수 있습니다.아마도 창조에 대한 퍼포먼스 타격은 충분히 견딜 수 있을 것입니다.하지만 우리의 주요 사용 사례인 reads도 휘발성 구성원에 대해 두 번 작동합니다.한 번이면 존재 여부를 확인하고, 다시 한 번 값을 반환합니다.


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

퍼포먼스 히트는 휘발성 멤버에서 직접 동작하기 때문에 로컬 변수를 휘발성 값으로 설정하고 대신 로컬 변수에서 동작합니다.이것에 의해, 휘발성의 조작 회수가 감소해, 퍼포먼스의 저하를 만회할 수 있습니다.동기화된 블록에 들어갈 때 로컬 변수를 다시 설정해야 합니다.그러면 잠금을 기다리는 동안 변경된 내용이 최신 상태로 유지됩니다.

저는 최근에 이것에 대한 기사를 썼습니다.싱글턴을 해체하는 중입니다.이러한 예에 대한 자세한 내용과 "보유자" 패턴의 예를 여기에서 찾을 수 있습니다.이중 체크된 휘발성 접근 방식을 보여주는 실제 사례도 있습니다.

Wikipedia에는 Java에서도 싱글톤의 예가 있습니다.Java 5의 실장은 매우 완벽해 보이며 스레드 세이프(이중 체크 잠금 적용)입니다.

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

느린 로딩, 논블로킹으로 스레드 세이프, 고성능.

로드를 게을리 할 필요가 없는 경우는, 다음의 조작을 실시해 주세요.

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 전체를 동기화하거나 모든 싱글톤의 레지스트리를 작성할 수도 있습니다.

나는 열거형 싱글톤이라고 말할 것이다.

Java에서 열거형을 사용하는 싱글톤은 일반적으로 열거형 싱글톤을 선언하는 방법입니다.열거형 싱글톤에는 인스턴스 변수 및 인스턴스 메서드를 포함할 수 있습니다.단순화를 위해 인스턴스 메서드를 사용하는 경우 오브젝트 상태에 영향을 미치는 경우 해당 메서드의 스레드 안전성을 확인해야 합니다.

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

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

은 하다, , 접속할 수 .Singleton.INSTANCE 라고요.getInstance()메서드를 지정합니다.

1.12 Enum 상수의 시리얼화

열거형 상수는 일반적인 직렬화 가능 개체 또는 외부화 가능 개체와 다르게 직렬화됩니다.열거 상수의 직렬화된 형식은 해당 이름으로만 구성되며, 상수의 필드 값은 형식에 없습니다.상수를 하려면 , 「」를 참조해 주세요.ObjectOutputStream는 열거 상수의 이름 메서드에 의해 반환된 값을 씁니다.상수를 , 「」를 참조해 주세요.ObjectInputStream그 후, 하려면 , 「」, 「」를 합니다.java.lang.Enum.valueOf메서드, 수신된 상수 이름과 함께 상수의 열거 형식을 인수로 전달합니다.열거형 상수는 다른 직렬화 가능 개체 또는 외부화 가능 개체와 마찬가지로 직렬화 스트림에 후속적으로 나타나는 백 참조의 대상으로 기능할 수 있습니다.

수 고유의 「」입니다. 클래스 고유writeObject,readObject,readObjectNoData,writeReplace , , , , 입니다.readResolve열거형으로 정의된 메서드는 시리얼화 및 역직렬화 중에 무시됩니다. 임의의 「」, 「」는serialPersistentFields ★★★★★★★★★★★★★★★★★」serialVersionUID "가 있습니다"가 .고치다serialVersionUID0L 이 없기 및 가 없습니다. 전송되는 데이터 유형에 변동이 없기 때문에 열거형에서 직렬화 가능한 필드 및 데이터를 문서화할 필요가 없습니다.

Oracle 문서에서 인용

의 또 다른 Serializable」이 된 것은, 「싱글톤」이 되어 있기 입니다.readObject()method 새로운 합니다. 문제는 '이러다'를 할 수 .readResolve()새로 생성된 인스턴스를 다음과 같이 싱글톤으로 대체하여 폐기합니다.

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

싱글톤 클래스가 상태를 유지하고 있는 경우는, 일시적인 것으로 할 필요가 있습니다만, Enum 싱글톤을 사용하면, JVM에 의해서 시리얼화가 보증됩니다.


읽기 양호

  1. 싱글턴 패턴
  2. Enum, Singleton 및 역직렬화
  3. 잠금과 싱글턴 패턴의 이중 체크

Java에서 싱글톤을 작성하는 방법은 4가지가 있습니다.

  1. 빠른 초기화 싱글톤

     public class Test {
         private static final Test test = new Test();
    
         private Test() {
         }
    
         public static Test getTest() {
             return test;
         }
     }
    
  2. 느린 초기화 싱글톤(스레드 세이프)

     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. 홀더 패턴 포함 빌 퍼 싱글톤 (최고 사양)

     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 싱글톤

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

심플한 싱글톤을 실장하는 방법은 다음과 같습니다.

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

싱글톤을 적절히 게으르게 작성하는 방법은 다음과 같습니다.

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

클래스의 인스턴스 변수를 느릿느릿 로드해야 하는 경우 이중 체크 관용구가 필요합니다.스태틱 변수 또는 싱글톤을 천천히 로드해야 하는 경우 Initialization on demand holder idi가 필요합니다.

또한 singleton 객체의 불변성을 유지하기 위해 singleton을 serializable로 할 필요가 있는 경우 다른 모든 필드는 transient여야 하며 readResolve() 메서드를 구현해야 합니다.그렇지 않으면 오브젝트가 역직렬화될 때마다 오브젝트의 새로운 인스턴스가 생성됩니다.readResolve()는 readObject()에 의해 읽혀진 새 개체를 대체하는 기능을 합니다.새 개체를 참조하는 변수가 없기 때문에 새 개체를 강제로 가비지 수집합니다.

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

싱글톤 오브젝트를 만드는 다양한 방법:

  1. Joshua Bloch에 따르면 Enum이 가장 좋을 것이다.

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

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

Enum 싱글톤

스레드 세이프 싱글톤을 구현하는 가장 간단한 방법은 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;
  }
}

volatile 키워드의 실장이 다르기 때문에 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 핵심 라이브러리의 GoF 설계 패턴 예시

가장 좋은 답변의 "싱글톤" 섹션에서

싱글톤(매회 같은 인스턴스(보통 그 자체)를 반환하는 크레이제이션 메서드로 인식 가능)

  • java.displaces를 클릭합니다.런타임 #getRuntime()
  • java.awt.데스크톱 #get Desktop()
  • java.displaces를 클릭합니다.시스템 #get Security Manager()

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

Java 1.5 이후 enum은 멀티 스레드 환경에서도 인스턴스가 1개만 생성되기 때문에 사용 가능한 최고의 싱글톤 구현이라고 생각합니다.

public enum Singleton {
    INSTANCE;
}

그리고 넌 끝났어!

단순한 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은 정적 { } - 블록을 실행합니다.그게 전부 비밀이야static-block은 이 1개의 클래스 로더에 의해 특정 패키지의 특정 클래스(이름)가 로드되는 시간에만 호출됩니다.

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 키워드를 추가했기 때문에 2개의 스레드가 동시에 getInstance를 호출하는 경우 레이스 조건을 회피했습니다.

언급URL : https://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java

반응형