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


14 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() 는 객체가 이전 프로그램 실행에서 직렬화 된 경우에도 유일한 인스턴스가 리턴 될 것임을 확인합니다.

Question

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




I still think after java 1.5, enum is the best available singleton implementation available as it also ensures that even in the multi threaded environments - only one instance is created.

public enum Singleton{ INSTANCE; }

and you are done !!!




To achieve this ( TRUE Singleton) ,

  1. Make sure that your class is final . Others can't sub-class it and create one more instance
  2. Make your singleton object as private static final
  3. Provide private constructor and public getInstance() method.
  4. Make sure that this Singleton class is loaded by one ClassLoader only
  5. override readResolve( ) method and return same instance.

Useful links: All answers in this post +

Singleton_pattern : Initialization-on-demand holder idiom from wikipedia

journaldev article

Demonstration of various methods of achieving Singleton

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

    public static Singleton getInstance(){
        return instance; 
    }
    public enum EnumSingleton {
        INSTANCE;   
    }
    private Object readResolve()  {
        return instance;
    }
    public static void main(String args[]){
        System.out.println("Singleton:"+Singleton.getInstance());
        System.out.println("Enum.."+EnumSingleton.INSTANCE);
        System.out.println("Lazy.."+LazySingleton.getInstance());
    }
}
final class  LazySingleton {
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
    private Object readResolve()  {
        return LazyHolder.INSTANCE;
    }
}

산출:

Singleton:Singleton@5eb1404f
Enum..INSTANCE
Lazy..LazySingleton@46fb3d6



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

싱글 톤을 구현하는 동안 우리는 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 에서 업데이트했습니다.




클래스의 인스턴스 변수를 느리게로드해야하는 경우에는 double-checking 관용구가 필요합니다. 정적 변수 또는 싱글 톤 지연을로드해야하는 경우 주문형 보유자 관용구 초기화 가 필요합니다.

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

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



나는 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. 더블 체크 잠금 및 싱글 톤 패턴



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




Might be a little late to the game on this, but there is a lot of nuance around implementing a singleton. The holder pattern can not be used in many situations. And IMO when using a volatile - you should also use a local variable. Let's start at the beginning and iterate on the problem. You'll see what I mean.

The first attempt might look something like this:

public class MySingleton {

     private static MySingleton INSTANCE;

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

        return INSTANCE;
    }
    ...
}

Here we have the MySingleton class which has a private static member called INSTANCE, and a public static method called getInstance(). The first time getInstance() is called, the INSTANCE member is null. The flow will then fall into the creation condition and create a new instance of the MySingleton class. Subsequent calls to getInstance() will find that the INSTANCE variable is already set, and therefore not create another MySingleton instance. This ensures there is only one instance of MySingleton which is shared among all callers of getInstance().

But this implementation has a problem. Multi-threaded applications will have a race condition on the creation of the single instance. If multiple threads of execution hit the getInstance() method at (or around) the same time, they will each see the INSTANCE member as null. This will result in each thread creating a new MySingleton instance and subsequently setting the INSTANCE member.

private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Here we've used the synchronized keyword in the method signature to synchronize the getInstance() method. This will certainly fix our race condition. Threads will now block and enter the method one at a time. But it also creates a performance problem. Not only does this implementation synchronize the creation of the single instance, it synchronizes all calls to getInstance(), including reads. Reads do not need to be synchronized as they simply return the value of INSTANCE. Since reads will make up the bulk of our calls (remember, instantiation only happens on the first call), we will incur an unnecessary performance hit by synchronizing the entire method.

private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Here we've moved synchronization from the method signature, to a synchronized block that wraps the creation of the MySingleton instance. But does this solve our problem? Well, we are no longer blocking on reads, but we've also taken a step backward. Multiple threads will hit the getInstance() method at or around the same time and they will all see the INSTANCE member as null. They will then hit the synchronized block where one will obtain the lock and create the instance. When that thread exits the block, the other threads will contend for the lock, and one by one each thread will fall through the block and create a new instance of our class. So we are right back where we started.

private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Here we issue another check from INSIDE the block. If the INSTANCE member has already been set, we'll skip initialization. This is called double-checked locking.

This solves our problem of multiple instantiation. But once again, our solution has presented another challenge. Other threads might not “see” that the INSTANCE member has been updated. This is because of how Java optimizes memory operations. Threads copy the original values of variables from main memory into the CPU's cache. Changes to values are then written to, and read from, that cache. This is a feature of Java designed to optimize performance. But this creates a problem for our singleton implementation. A second thread — being processed by a different CPU or core, using a different cache — will not see the changes made by the first. This will cause the second thread to see the INSTANCE member as null forcing a new instance of our singleton to be created.

private static volatile MySingleton INSTANCE;

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

    return INSTANCE;
}

We solve this by using the volatile keyword on the declaration of the INSTANCE member. This will tell the compiler to always read from, and write to, main memory, and not the CPU cache.

But this simple change comes at a cost. Because we are bypassing the CPU cache, we will take a performance hit each time we operate on the volatile INSTANCE member — which we do 4 times. We double-check existence (1 and 2), set the value (3), and then return the value (4). One could argue that this path is the fringe case as we only create the instance during the first call of the method. Perhaps a performance hit on creation is tolerable. But even our main use-case, reads, will operate on the volatile member twice. Once to check existence, and again to return its value.

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

Since the performance hit is due to operating directly on the volatile member, let's set a local variable to the value of the volatile and operate on the local variable instead. This will decrease the number of times we operate on the volatile, thereby reclaiming some of our lost performance. Note that we have to set our local variable again when we enter the synchronized block. This ensures it is up to date with any changes that occured while we were waiting for the lock.

I wrote an article about this recently. Deconstructing The Singleton . You can find more info on these examples and an example of the "holder" pattern there. There is also a real-world example showcasing the double-checked volatile approach. 희망이 도움이됩니다.




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




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를 호출하는 경우 경쟁 조건을 피할 수 있습니다.




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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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



For JSE 5.0 and above take the Enum approach, otherwise use static singleton holder approach ( (a lazy loading approach described by Bill Pugh). Latter solution is also thread-safe without requiring special language constructs (ie volatile or synchronized).




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

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

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




열거 형 싱글 톤

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

This is not thread-safe before 1.5 because the implementation of the volatile keyword was different.

Early loading Singleton (works even before Java 1.5)

This implementation instantiates the singleton when the class is loaded and provides thread safety.

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

}



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




Related