Java의 단례 모드 7가지 쓰기

5445 단어 Java단일 모드
첫 번째 (게으름뱅이, 라인이 안전하지 않음):

public class Singleton {
  private static Singleton instance;
  private Singleton (){}

  public static Singleton getInstance() {
 if (instance == null) {
   instance = new Singleton();
 }
 return instance;
  }
}
이런 작법lazyloading은 매우 뚜렷하지만, 치명적인 것은 다선정에서 정상적으로 일을 할 수 없다는 것이다.
두 번째 (게으름뱅이, 스레드 안전):

public class Singleton {
  private static Singleton instance;
  private Singleton (){}
  public static synchronized Singleton getInstance() {
 if (instance == null) {
   instance = new Singleton();
 }
 return instance;
  }
}
이런 작법은 다중 노드에서 좋은 작업을 할 수 있을 뿐만 아니라, 보기에도 매우 좋은lazyloading을 갖추고 있지만, 유감스럽게도 효율이 매우 낮고, 99퍼센트의 상황에서 동기화할 필요가 없다.
세 번째(굶주린 사람):

public class Singleton {
  private static Singleton instance = new Singleton();
  private Singleton (){}
  public static Singleton getInstance() {
 return instance;
  }
}
이런 방식은classloder 메커니즘을 바탕으로 다중 스레드의 동기화 문제를 피했다. 그러나 instance는 클래스를 불러올 때 실례화되었다. 클래스 불러오는 원인은 여러 가지가 있지만 단일 모드에서 대부분이 get Instance 방법을 호출하지만 다른 방식(또는 다른 정적 방법)이 클래스 불러오는 것을 확정할 수 없다. 이때 instance를 초기화하는 것은lazyloading의 효과에 이르지 못했다.
네 번째(굶주린 사람, 변종):

public class Singleton {
  private Singleton instance = null;
  static {
 instance = new Singleton();
  }
  private Singleton (){}
  public static Singleton getInstance() {
 return this.instance;
  }
}
표면적으로 보면 차이가 매우 커 보이지만 사실은 세 번째 방식이 차이가 많지 않다. 모두 클래스 초기화 즉 실례화 instance이다.
다섯 번째 (정적 내부 클래스):

public class Singleton {
  private static class SingletonHolder {
 private static final Singleton INSTANCE = new Singleton();
  }
  private Singleton (){}
  public static final Singleton getInstance() {
 return SingletonHolder.INSTANCE;
  }
}
이런 방식은 역시classloder의 메커니즘을 이용하여 instance를 초기화할 때 하나의 라인만 확보한다. 이것은 세 번째와 네 번째 방식과 다르다. 세 번째와 네 번째 방식은 Singleton 클래스가 불러오기만 하면 instance가 실례화된다(lazyloading 효과에 이르지 못한다). 이런 방식은 Singleton 클래스가 불러오기 때문에 instance가 반드시 초기화되는 것은 아니다.Singleton Holder 클래스는 주동적으로 사용되지 않기 때문에 get Instance 방법을 호출할 때만 Singleton Holder 클래스를 불러오는 것을 보여줌으로써 인스턴스를 실례화합니다.만약에 실례화된 instance가 자원을 소모한다면 나는 그로 하여금 불러오는 것을 늦추게 하고 싶다. 다른 한편, 나는 Singleton 클래스가 불러올 때 실례화하는 것을 원하지 않는다. 왜냐하면 나는 Singleton 클래스가 다른 곳에서 주동적으로 사용되고 불러올 수 있다는 것을 확보할 수 없기 때문이다. 그러면 이때 실례화된 instance는 분명히 적합하지 않다.이때 이런 방식은 세 번째와 네 번째 방식에 비해 매우 합리적으로 보인다.
여섯 번째 (열거):

public enum Singleton {
  INSTANCE;
  public void whateverMethod() {
  }
}
이런 방식은 Effective Java 저자 Josh Bloch가 제창한 방식으로 다중 스레드 동기화 문제를 피할 수 있을 뿐만 아니라 반서열화를 방지하고 새로운 대상을 다시 만드는 것을 방지하는 튼튼한 장벽이라고 할 수 있다. 그러나 개인적으로는 1.5에 enum 특성을 넣었기 때문에 이런 방식으로 쓰는 것이 생소하다고 생각한다. 실제 업무에서 나도 이렇게 쓰는 사람을 본 적이 매우 드물다.
일곱 번째(이중 체크 자물쇠):

public class Singleton {
  private volatile static Singleton singleton;
  private Singleton (){}
  public static Singleton getSingleton() {
 if (singleton == null) {
   synchronized (Singleton.class) {
 if (singleton == null) {
   singleton = new Singleton();
 }
   }
 }
 return singleton;
  }
}

이것은 두 번째 방식의 업그레이드 버전입니다. 속칭 이중 검사 잠금이라고 합니다. 상세한 소개는 보십시오http://www.ibm.com/developerworks/cn/java/j-dcl.html
JDK1.5 이후에야 이중 잠금이 정상적으로 단일 효과에 도달할 수 있습니다.
총결산
다음과 같은 두 가지 문제가 있습니다.
1. 하나의 예가 다른 클래스 마운터에 불러오면 여러 개의 단일 클래스의 실례가 존재할 수 있다.원격 접근이 아니라고 가정합니다. 예를 들어 일부 servlet 용기는 모든 servlet에 대해 완전히 다른 클래스 마운트를 사용합니다. 그러면 두 개의 servlet이 하나의 클래스에 접근하면 각각의 실례가 있습니다.
2. 만약 Singleton이 자바를 실현했다면.io.Serializable 인터페이스, 그러면 이 종류의 실례는 서열화되고 복원될 수 있습니다.어쨌든, 만약 당신이 하나의 단례류의 대상을 서열화하고, 다음에 여러 개의 그 대상을 복원한다면, 당신은 여러 개의 단례류의 실례를 가지고 있을 것이다.
첫 번째 문제를 해결하는 방법은 다음과 같습니다.
private static Class getClass(String classname)   
                                         throws ClassNotFoundException {  
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();  
   
      if(classLoader == null)  
         classLoader = Singleton.class.getClassLoader();  
   
      return (classLoader.loadClass(classname));  
   }  
}
두 번째 문제에 대한 복구 방법은
public class Singleton implements java.io.Serializable {  
   public static Singleton INSTANCE = new Singleton();  
   
   protected Singleton() {  
     
   }  
   private Object readResolve() {  
            return INSTANCE;  
      } 
}
저에게 있어서 저는 세 번째와 다섯 번째 방식을 비교적 좋아합니다. 간단하고 이해하기 쉬우며 JVM층에서 스레드 안전(여러 종류의 캐리어 환경이 아니라면)을 실현했습니다. 일반적인 상황에서 저는 세 번째 방식을 사용합니다. lazyloading 효과를 명확하게 실현해야 다섯 번째 방식을 사용합니다. 또한 반서열화 창설 대상과 관련이 있을 때 저는 매거적인 방식으로 예를 들어 보겠습니다.나는 줄곧 나의 프로그램이 안전하다는 것을 보장할 것이다. 그리고 나는 영원히 첫 번째와 두 번째 방식을 사용하지 않을 것이다. 만약에 다른 특수한 수요가 있다면 나는 일곱 번째 방식을 사용할 것이다. 왜냐하면 JDK1.5는 이미 이중 검사 잠금 문제가 없기 때문이다.
========================================================================
Superheizai 학우들이 잘 정리했습니다.
 
그러나 일반적으로 첫 번째는 단례가 아니라 네 번째와 세 번째는 하나이며, 계산하면 다섯 번째도 따로 쓸 수 있다.그래서 일반적인 단례는 모두 다섯 가지 기법이다.게으름뱅이, 악한, 이중 검사 자물쇠, 매거와 정적 내부류.
나는 이런 독자가 있어서 함께 노력하게 되어 매우 기쁘다.

좋은 웹페이지 즐겨찾기