Java에서 간단하지 않은 단례 패턴을 깊이 이해하다

5966 단어 java단일 모드
앞말
Java의 Singleton 모델은 널리 사용되는 디자인 모델이라는 것을 모두가 알고 있다.단례 모델의 주요 역할은 자바 프로그램에서 어떤 종류는 하나의 실례만 존재한다는 것을 확보하는 것이다.일부 관리자와 컨트롤러는 자주 단일 모드로 설계된다.
단일 모드는 많은 장점을 가진다. 실례 대상의 중복 창설을 피할 수 있고 매번 창설 대상의 시간 비용을 줄일 수 있을 뿐만 아니라 메모리 공간도 절약할 수 있다.여러 개의 실례를 조작하여 발생하는 논리적 오류를 피할 수 있다.만약에 하나의 대상이 전체 응용 프로그램을 관통시키고 전체적인 통일된 관리 제어 역할을 할 수 있다면 단일 모델은 고려할 만한 선택일 수도 있다.
단례 모델은 여러 가지 기법이 있는데 대부분 기법이 많거나 적거나 부족하다.다음은 이 몇 가지 작법에 대해 각각 소개할 것이다.
1. 굶주린 남자 모드

public class Singleton{ 
 private static Singleton instance = new Singleton(); 
 private Singleton(){} 
 public static Singleton newInstance(){ 
 return instance; 
 } 
} 
코드에서 알 수 있듯이 클래스의 구조 함수는private로 정의되어 다른 클래스가 이런 종류를 실례화할 수 없도록 한 다음에 정적 실례를 제공하고 호출자에게 되돌려줍니다.굶주린 모드는 가장 간단한 실현 방식이다. 굶주린 모드는 클래스가 불러올 때 실례를 만들고 실례는 전체 프로그램 주기에 존재한다.그것의 장점은 클래스가 불러올 때만 실례를 만들고, 여러 개의 스레드가 여러 개의 실례를 만드는 상황이 존재하지 않으며, 여러 개의 스레드가 동기화되는 문제를 피할 수 있다는 것이다.이 단례가 사용되지 않아도 생성되고, 클래스가 불러온 후에 생성되어 메모리가 낭비된다는 단점도 뚜렷하다.
이런 실현 방식은 한 번에 메모리를 적게 차지하고 초기화할 때 사용되는 상황에 적합하다.그러나 만약에 한 번이 차지하는 메모리가 비교적 크거나 한 번이 특정한 장면에서만 사용할 수 있다면 굶주린 모드를 사용하는 것은 적합하지 않다. 이럴 때 게으른 모드로 로드 지연을 해야 한다.
2. 게으름뱅이 모드

public class Singleton{ 
 private static Singleton instance = null; 
 private Singleton(){} 
 public static Singleton newInstance(){ 
 if(null == instance){ 
  instance = new Singleton(); 
 } 
 return instance; 
 } 
} 
게으름뱅이 모드에서 한 예는 필요할 때 만드는 것입니다. 한 예가 이미 만들어졌으면 다시 호출해서 인터페이스를 가져오면 새로운 대상을 다시 만들지 않고 이전에 만든 대상을 직접 되돌려줍니다.만약에 어떤 단례가 사용되는 횟수가 적고 단례를 만드는 데 소모되는 자원이 많다면 단례의 필요에 따라 창설을 실현해야 한다. 이때 게으름뱅이 모드를 사용하는 것이 좋은 선택이다.그러나 이곳의 게으름뱅이 모드는 스레드 안전 문제를 고려하지 않습니다. 여러 스레드에서 get Instance () 방법을 병렬적으로 호출하여 여러 개의 실례를 만들 수 있기 때문에 스레드 동기화 문제를 해결하기 위해 자물쇠를 채워야 합니다.

public class Singleton{ 
 private static Singleton instance = null; 
 private Singleton(){} 
 public static synchronized Singleton newInstance(){ 
 if(null == instance){ 
  instance = new Singleton(); 
 } 
 return instance; 
 } 
} 
3. 이중 검증 자물쇠
자물쇠를 넣는 게으름뱅이 모드는 라인 병발 문제를 해결하고 로드 지연을 실현한 것처럼 보이지만 성능 문제가 존재하고 있어 여전히 완벽하지 않다.synchronized 수식의 동기화 방법은 일반적인 방법보다 훨씬 느리다. 만약에 여러 번 호출getInstance() 하면 누적된 성능 손실이 비교적 크다.그래서 이중 검사 자물쇠가 생겼습니다. 먼저 실행 코드를 보십시오.

public class Singleton { 
 private static Singleton instance = null; 
 private Singleton(){} 
 public static Singleton getInstance() { 
 if (instance == null) { 
  synchronized (Singleton.class) { 
  if (instance == null) {//2 
   instance = new Singleton(); 
  } 
  } 
 } 
 return instance; 
 } 
} 
위에서 동기화 코드 블록 밖에 instance가 비어 있다는 판단을 볼 수 있습니다.단일 대상을 한 번만 만들 수 있기 때문에, 다음에 다시 호출할 경우 getInstance() 단일 대상을 직접 되돌려주기만 하면 됩니다.따라서 대부분의 경우 getInstance () 호출은 동기화 코드 블록에 실행되지 않아 프로그램 성능을 향상시킨다.그러나 한 가지 상황을 고려해야 한다. 만약에 두 개의 스레드 A, B, A가 if (instance == null) 문장을 실행했다면 이것은 하나의 대상이 창설되지 않았다고 생각할 것이다. 이때 스레드가 B로 절단되어도 같은 문장을 집행했고 B도 하나의 대상이 창설되지 않았다고 생각한 다음에 두 개의 스레드가 순서대로 동기화 코드 블록을 집행하고 각각 하나의 하나의 단일 대상을 만들었다.이 문제를 해결하기 위해서는 동기화 코드 블록에 if (instance == null) 문장, 즉 위에서 본 코드 2를 추가해야 한다.
우리는 이중 검사 자물쇠가 지연 적재를 실현하고 라인의 병발 문제를 해결하는 동시에 집행 효율 문제를 해결하는 것을 보았는데 정말 만전을 기한 것이 아니겠는가?
여기서 Java의 명령어 재배열 최적화를 언급합니다.지령 리셋 최적화란 원어의 의미를 바꾸지 않고 지령의 집행 순서를 조정하여 프로그램을 더욱 빨리 운행하는 것을 말한다.JVM에는 컴파일러 최적화와 관련된 내용이 규정되어 있지 않습니다. 즉, JVM은 명령어 재정렬의 최적화를 자유롭게 진행할 수 있습니다.
이 문제의 관건은 명령 재배열 최적화의 존재로 인해 Singleton을 초기화하고 대상 주소를 instance 필드에 부여하는 순서가 확실하지 않다는 데 있다.어떤 라인에서 단일 대상을 만들 때, 구조 방법이 호출되기 전에, 이 대상에 메모리 공간을 분배하고, 대상의 필드를 기본값으로 설정합니다.이 때 할당된 메모리 주소를 instance 필드에 지정할 수 있지만, 이 대상은 초기화되지 않았을 수도 있습니다.getInstance를 호출하기 위해 다른 라인을 따라가면 상태가 올바르지 않은 대상을 찾으면 프로그램이 오류가 발생합니다.
이상은 이중 검사 자물쇠가 효력을 잃는 원인이지만 다행히 JDK1.5 및 이후 버전에volatile 키워드가 추가되었습니다.volatile의 한 의미는 명령의 정렬 최적화를 금지하고 instance 변수가 값을 부여할 때 대상이 초기화되어 위에서 말한 문제를 피할 수 있다는 것이다.
코드는 다음과 같습니다.

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; 
 } 
} 
4. 정적 내부 클래스
위의 세 가지 방식을 제외하고 또 다른 실현 단례의 방식은 정적 내부류를 통해 실현된다.먼저 구현 코드를 살펴보겠습니다.

public class Singleton{ 
 private static class SingletonHolder{ 
 public static Singleton instance = new Singleton(); 
 } 
 private Singleton(){} 
 public static Singleton newInstance(){ 
 return SingletonHolder.instance; 
 } 
} 
이런 방식은 클래스 불러오는 메커니즘을 이용하여 하나의 instance 실례만 만드는 것을 보장한다.그것은 굶주린 모델과 마찬가지로 클래스 로드 메커니즘을 이용했기 때문에 다중 스레드 병발 문제는 존재하지 않는다.다른 것은 내부 클래스에서 대상을 만드는 실례입니다.이렇게 하면 응용 프로그램에서 내부 클래스를 사용하지 않는 한 JVM은 이 클래스를 불러오지 않고 단일 대상을 만들지 않아 게으름뱅이 식의 지연 불러오기를 실현할 수 있다.즉, 이런 방식은 로드 지연과 스레드 안전을 동시에 보장할 수 있다.
5, 매거
다시 보면 본고가 소개하고자 하는 마지막 실현 방식은 매거이다.

public enum Singleton{ 
 instance; 
 public void whateverMethod(){} 
} 
앞에서 언급한 네 가지 실현 단례의 방식은 모두 공통된 결점이 있다.
1) 서열화를 위한 추가 작업이 필요합니다. 그렇지 않으면 서열화된 대상을 반서열화할 때마다 새로운 실례를 만듭니다.
2) 반사로 개인 구조기를 강제로 호출할 수 있다(이 상황을 피하려면 구조기를 수정해서 두 번째 실례를 만들 때 이상을 던질 수 있다).
한편, 매거류는 이 두 가지 문제를 잘 해결했다. 매거를 사용하면 스레드 안전과 반사 호출 방지 구조기 외에 자동 서열화 메커니즘을 제공하여 반서열화를 방지할 때 새로운 대상을 만들 수 있다.따라서 Effective Java 작성자가 권장하는 방법입니다.그러나 실제 업무에서 이렇게 쓰는 사람은 매우 드물다.
총결산
본고는 다섯 가지 자바에서 단례를 실현하는 방법을 정리했는데 그 중에서 앞의 두 가지 모두 완벽하지 않다. 이중 검사 자물쇠와 정적 내부 유형의 방식은 대부분의 문제를 해결할 수 있고 평소에 작업에서 가장 많이 사용하는 것도 이 두 가지 방식이다.매거 방식은 여러 가지 문제를 완벽하게 해결했지만 이런 작법은 다소 생소하게 느껴진다.개인의 건의는 특수한 수요가 없는 상황에서 세 번째와 네 번째 방식으로 단일 모델을 실현하는 것이다.
이상은 바로 이 글의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 어느 정도 도움이 되고 의문이 있으면 댓글로 교류하시기 바랍니다.

좋은 웹페이지 즐겨찾기