Java에서 싱글 톤 패턴을 구현하는 효율적인 방법은 무엇입니까? [닫은]
Java에서 싱글 톤 패턴을 구현하는 효율적인 방법은 무엇입니까?
열거 형 사용 :
public enum Foo {
INSTANCE;
}
Joshua Bloch는 Google I / O 2008의 Effective Java Reloaded 강연 : 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" 의 온라인 부분 은 다음과 같이 말합니다.
"이 접근 방식은 더 간결하고, 직렬화 기계를 무료로 제공하고, 정교한 직렬화 또는 반사 공격에도 불구하고 다중 인스턴스화에 대한 철갑 한 보증을 제공한다는 점을 제외하면 공용 필드 접근 방식과 기능적으로 동일합니다. 아직 널리 채택되지는 않았지만 단일 요소 열거 형은 단일 항목을 구현하는 가장 좋은 방법 입니다. "
사용법에 따라 몇 가지 "정답"이 있습니다.
java5 이후 가장 좋은 방법은 열거 형을 사용하는 것입니다.
public enum Foo {
INSTANCE;
}
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를 만들지 못하도록 생성자를 비공개로 만들어야합니다. 생성자에서 예외를 던지면 사용자가 리플렉션을 사용하여 두 번째 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 클래스가 실제로 사용될 때만 실행 되기 때문에 지연 인스턴스화를 처리하고 스레드로부터 안전합니다.
또한 개체를 직렬화 할 수 있도록하려면 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()
는 개체가 이전 프로그램 실행에서 직렬화 된 경우에도 유일한 인스턴스가 반환되도록합니다.
면책 조항 : 방금 모든 멋진 답변을 요약하고 내 말로 썼습니다.
Singleton을 구현하는 동안 두 가지 옵션이 있습니다
. 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
가 실제로 사용될 때만 실행 되기 때문에 지연 인스턴스화를 처리합니다.
스레드 안전이 보장됩니다.
그리고 우리는 지금까지 왔으며, 우리가 한 모든 것을 달성하는 가장 좋은 방법은 가능한 최선의 방법입니다.
public enum Foo {
INSTANCE;
}
내부적으로 다음과 같이 처리됩니다.
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
그것은 더 이상 직렬화, 스레드 및 추악한 코드에 대한 두려움이 아닙니다. 또한 ENUMS 싱글 톤은 느리게 초기화 됩니다.
이 접근 방식은 더 간결하고, 직렬화 기계를 무료로 제공하고, 정교한 직렬화 또는 반사 공격에도 불구하고 다중 인스턴스화에 대한 철갑 한 보증을 제공한다는 점을 제외하면 공용 필드 접근 방식과 기능적으로 동일합니다. 이 접근 방식은 아직 널리 채택되지 않았지만 단일 요소 열거 형은 단일 항목을 구현하는 가장 좋은 방법입니다.
- "Effective Java"의 Joshua Bloch
이제 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를 사용하면 테스트 목적으로 모의 리소스를 사용할 수 있으므로 단위 테스트를 사용하는 경우 특히 종속성 주입이 더 유용하다는 것을 알았습니다.
싱글 톤 사용에 대한 대안으로 DI를 제안하는 일부 답변에 대해 궁금합니다. 이것은 관련없는 개념입니다. DI를 사용하여 싱글 톤 또는 비 싱글 톤 (예 : 스레드 당) 인스턴스를 삽입 할 수 있습니다. 적어도 Spring 2.x를 사용한다면 사실입니다. 다른 DI 프레임 워크에 대해서는 말할 수 없습니다.
따라서 OP에 대한 내 대답은 (가장 사소한 샘플 코드를 제외하고 모두) 다음과 같습니다.
- Spring과 같은 DI 프레임 워크를 사용하고
- 종속성이 싱글 톤, 요청 범위, 세션 범위 등 무엇이든 DI 구성의 일부로 만드십시오.
이 접근 방식은 단일 항목을 사용할지 여부가 쉽게 되돌릴 수있는 구현 세부 사항 (물론 사용하는 모든 단일 항목이 스레드로부터 안전한 경우) 인 멋진 분리 된 (따라서 유연하고 테스트 가능한) 아키텍처를 제공합니다.
작성하기 전에 싱글 톤이 필요한 이유를 정말로 고려하십시오. 자바에서 싱글 톤을 검색하면 쉽게 우연히 발견 할 수있는 사용에 대한 준 종교적 논쟁이 있습니다.
개인적으로 저는 여러 가지 이유로 가능한 한 자주 싱글 톤을 피하려고 노력합니다. 대부분의 경우 싱글 톤 검색을 통해 찾을 수 있습니다. 싱글 톤은 모두가 이해하기 쉽기 때문에 오용되는 경우가 많고, "글로벌"데이터를 OO 설계로 가져 오는 메커니즘으로 사용되며, 객체 수명주기 관리를 우회하기 쉽기 때문에 사용됩니다 (또는 B) 안에서 A를 할 수있는 방법에 대해 정말로 생각하고 있습니다. IoC (Inversion of Control) 또는 DI (Dependency Injection)와 같은 것을 살펴보십시오.
정말로 필요하다면 wikipedia에 싱글 톤의 적절한 구현에 대한 좋은 예가 있습니다.
다음은 세 가지 접근 방식입니다.
1) 열거 형
/**
* Singleton pattern example using Java Enumj
*/
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{
//initailzed 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;
}
}
저는 Spring Framework를 사용하여 싱글 톤을 관리합니다. 클래스의 "단일성"을 강요하지는 않지만 (여러 클래스 로더가 관련되어 있으면 어쨌든 실제로 할 수 없습니다) 다양한 유형의 객체를 생성하기 위해 다양한 팩토리를 구축하고 구성하는 정말 쉬운 방법을 제공합니다.
버전 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;
}
}
지연 로딩, 비 블로킹으로 스레드 안전, 고성능.
Wikipedia에는 Java에서도 싱글 톤의 몇 가지 예가 있습니다. Java 5 구현은 매우 완전 해 보이며 스레드로부터 안전합니다 (이중 검사 잠금 적용).
지연 로딩이 필요하지 않으면 간단히 시도하십시오.
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();
}
}
지연로드를 원하고 Singleton이 스레드로부터 안전하도록하려면 이중 검사 패턴을 시도하십시오.
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 메서드를 동기화하거나 모든 싱글 톤에 대한 레지스트리를 만들 수도 있습니다.
나는 Enum 싱글 톤이라고 말할 것이다
Java에서 enum을 사용하는 Singleton은 일반적으로 enum singleton을 선언하는 방법입니다. 열거 형 싱글 톤은 인스턴스 변수와 인스턴스 메소드를 포함 할 수 있습니다. 단순성을 위해 인스턴스 메서드를 사용하는 경우 해당 메서드의 스레드 안전성을 보장해야하는 것보다 객체 상태에 영향을 미치는 경우에도 유의하십시오.
열거 형의 사용은 구현하기가 매우 쉬우 며 다른 방법으로 피해야하는 직렬화 가능한 객체와 관련된 단점이 없습니다.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Singleton에서 메서드를 Singleton.INSTANCE
호출 getInstance()
하는 것보다 훨씬 쉽게 액세스 할 수 있습니다 .
1.12 열거 형 상수 직렬화
열거 형 상수는 일반 직렬화 가능 또는 외부화 가능 개체와 다르게 직렬화됩니다. 열거 형 상수의 직렬화 된 형식은 이름으로 만 구성됩니다. 상수의 필드 값이 양식에 없습니다. 열거 형 상수를 직렬화하려면 열거
ObjectOutputStream
형 상수의 이름 메서드에서 반환 한 값을 씁니다. 열거 형 상수를 역 직렬화하려면ObjectInputStream
스트림에서 상수 이름을 읽습니다. 그런 다음 역 직렬화 된 상수는java.lang.Enum.valueOf
메서드 를 호출 하고 수신 된 상수 이름과 함께 상수의 열거 형을 인수로 전달하여 얻습니다 . 다른 직렬화 가능 또는 외부화 가능 개체와 마찬가지로 열거 형 상수는 직렬화 스트림에서 이후에 나타나는 역 참조의 대상으로 작동 할 수 있습니다.열거 상수가 직렬화하는 프로세스를 정의 할 수 없습니다 : 모든 클래스 별은
writeObject
,readObject
,readObjectNoData
,writeReplace
, 및readResolve
enum 형에 의해 정의 된 메소드는 직렬화 및 역 직렬화하는 동안 무시됩니다. 마찬가지로, 어떤serialPersistentFields
또는serialVersionUID
필드 선언도 무시됩니다 - 모든 열거 타입은 해결 한serialVersionUID
의0L
. 직렬화 가능한 필드 및 열거 형 유형에 대한 데이터를 문서화 할 필요가 없습니다. 전송되는 데이터 유형에는 변화가 없기 때문입니다.
기존 Singleton의 또 다른 문제는 Serializable
인터페이스 를 구현하면 readObject()
메소드가 항상 Java의 생성자와 같은 새 인스턴스를 반환 하기 때문에 더 이상 Singleton으로 유지되지 않는다는 것 입니다. readResolve()
아래와 같이 싱글 톤으로 교체하여 새로 생성 된 인스턴스를 사용 하고 폐기 함으로써이를 피할 수 있습니다.
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Singleton Class가 상태를 유지하면 일시적으로 만들 필요가 있기 때문에 이것은 훨씬 더 복잡해질 수 있지만 Enum Singleton에서는 JVM에 의해 직렬화가 보장됩니다.
좋은 읽기
이것에 대한 게임에 조금 늦을 수 있지만 싱글 톤 구현에 대한 많은 뉘앙스가 있습니다. 홀더 패턴은 많은 상황에서 사용할 수 없습니다. 휘발성을 사용할 때 IMO-지역 변수도 사용해야합니다. 처음부터 시작하여 문제를 반복 해 보겠습니다. 무슨 말인지 알 수있을 겁니다.
첫 번째 시도는 다음과 같습니다.
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
여기에는 INSTANCE라는 개인 정적 멤버와 getInstance ()라는 공용 정적 메서드가있는 MySingleton 클래스가 있습니다. getInstance ()가 처음 호출 될 때 INSTANCE 멤버는 널입니다. 그러면 흐름이 생성 조건에 해당하고 MySingleton 클래스의 새 인스턴스가 생성됩니다. getInstance ()에 대한 후속 호출은 INSTANCE 변수가 이미 설정되어 있으므로 다른 MySingleton 인스턴스를 만들지 않습니다. 이렇게하면 getInstance ()의 모든 호출자간에 공유되는 MySingleton 인스턴스가 하나만 있습니다.
그러나이 구현에는 문제가 있습니다. 다중 스레드 응용 프로그램에는 단일 인스턴스 생성에 대한 경쟁 조건이 있습니다. 여러 스레드의 실행이 동시에 (또는 그 주변) getInstance () 메서드에 도달하면 각각 INSTANCE 멤버가 null로 표시됩니다. 이렇게하면 각 스레드가 새 MySingleton 인스턴스를 만들고 이후에 INSTANCE 멤버를 설정하게됩니다.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
여기서는 getInstance () 메서드를 동기화하기 위해 메서드 서명에 동기화 된 키워드를 사용했습니다. 이것은 우리의 경쟁 조건을 확실히 고칠 것입니다. 스레드는 이제 한 번에 하나씩 차단하고 메소드에 들어갑니다. 하지만 성능 문제도 발생합니다. 이 구현은 단일 인스턴스 생성을 동기화 할뿐만 아니라 읽기를 포함하여 getInstance ()에 대한 모든 호출을 동기화합니다. 읽기는 단순히 INSTANCE 값을 반환하므로 동기화 할 필요가 없습니다. 읽기는 호출의 대부분을 구성하므로 (인스턴스화는 첫 번째 호출에서만 발생 함) 전체 메서드를 동기화하여 불필요한 성능 저하가 발생합니다.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
여기서는 메서드 서명에서 MySingleton 인스턴스 생성을 래핑하는 동기화 된 블록으로 동기화를 이동했습니다. 그러나 이것이 우리의 문제를 해결합니까? 글쎄, 우리는 더 이상 읽기를 차단하지 않지만 한 걸음 뒤로 물러났습니다. 여러 스레드가 동시에 또는 거의 동시에 getInstance () 메서드에 도달하고 모두 INSTANCE 멤버를 null로 간주합니다. 그런 다음 동기화 된 블록에 도달하여 잠금을 획득하고 인스턴스를 생성합니다. 그 쓰레드가 블록을 빠져 나가면 다른 쓰레드가 잠금을 놓고 경쟁하고 각 쓰레드가 하나씩 블록을 통과하여 클래스의 새 인스턴스를 생성합니다. 그래서 우리는 우리가 시작한 곳으로 바로 돌아 왔습니다.
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 멤버를 null로 간주하여 싱글 톤의 새 인스턴스가 생성되도록합니다.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
INSTANCE 멤버 선언에 volatile 키워드를 사용하여이 문제를 해결합니다. 이것은 컴파일러에게 항상 CPU 캐시가 아닌 주 메모리에서 읽고 쓰도록 지시합니다.
그러나이 간단한 변화에는 비용이 듭니다. CPU 캐시를 우회하고 있기 때문에 휘발성 INSTANCE 멤버에서 작업 할 때마다 성능 저하가 발생합니다.이 작업은 4 번 수행합니다. 존재 여부 (1 및 2)를 다시 확인하고 값 (3)을 설정 한 다음 값 (4)을 반환합니다. 메서드를 처음 호출 할 때만 인스턴스를 생성하므로이 경로가 부가적인 경우라고 주장 할 수 있습니다. 아마도 창조에 대한 성능 저하는 견딜 수있을 것입니다. 그러나 우리의 주요 사용 사례 인 읽기도 휘발성 멤버에서 두 번 작동합니다. 한 번은 존재를 확인하고 다시 값을 반환합니다.
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;
}
성능 저하는 volatile 멤버에서 직접 작동하기 때문에 발생하므로 로컬 변수를 volatile 값으로 설정하고 대신 로컬 변수에서 작동 해 보겠습니다. 이렇게하면 휘발성에서 작업하는 횟수가 줄어들어 손실 된 성능의 일부를 되 찾을 수 있습니다. 동기화 된 블록에 들어갈 때 다시 로컬 변수를 설정해야합니다. 이렇게하면 잠금을 기다리는 동안 발생한 모든 변경 사항이 최신 상태로 유지됩니다.
최근에 이것에 대한 기사를 썼습니다. 싱글 톤 해체 . 이 예제에 대한 자세한 정보와 "홀더"패턴 예제를 찾을 수 있습니다. 이중 확인 된 휘발성 접근 방식을 보여주는 실제 사례도 있습니다. 도움이 되었기를 바랍니다.
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
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- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
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 singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
다음은 간단한 구현 방법입니다 singleton
.
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;
}
}
이것이 제대로 게으른 생성 방법입니다 singleton
.
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();
}
}
당신은 필요가 두 번 확인 관용구를 사용하면 게으르게 클래스의 인스턴스 변수를로드해야합니다. 정적 변수 또는 싱글 톤을 느리게로드해야하는 경우 주문형 홀더 관용구 초기화 가 필요합니다 .
또한 싱글 톤이 연속적이어야하는 경우 다른 모든 필드는 일시적이어야하며 싱글 톤 객체를 불변으로 유지하기 위해 readResolve () 메서드를 구현해야합니다. 그렇지 않으면 개체가 역 직렬화 될 때마다 개체의 새 인스턴스가 만들어집니다. readResolve ()가하는 일은 readObject ()에서 읽은 새 객체를 대체하는 것입니다.이 객체를 참조하는 변수가 없기 때문에 새 객체가 가비지 수집됩니다.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
싱글 톤 객체를 만드는 다양한 방법 :
Joshua Bloch에 따르면-Enum이 최고입니다.
이중 확인 잠금을 사용할 수도 있습니다.
내부 정적 클래스도 사용할 수 있습니다.
단일 항목 열거
스레드로부터 안전한 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 ;
}
}
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 1.5 이후에도 enum은 다중 스레드 환경에서도 하나의 인스턴스 만 생성되도록 보장하기 때문에 사용 가능한 최상의 싱글 톤 구현이라고 생각합니다.
public enum Singleton{ INSTANCE; }
그리고 당신은 끝났습니다 !!!
이 게시물을 살펴보십시오.
Java의 핵심 라이브러리에있는 GoF 디자인 패턴의 예
베스트 답변의 "Singleton"섹션에서
Singleton (매번 동일한 인스턴스 (일반적으로 자체)를 반환하는 생성 방법으로 인식 가능)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Java 네이티브 클래스 자체에서 Singleton의 예를 배울 수도 있습니다.
내가 본 최고의 싱글 톤 패턴은 Supplier 인터페이스를 사용합니다.
- 일반적이고 재사용 가능합니다.
- 지연 초기화를 지원합니다.
- 초기화 될 때까지만 동기화되고 차단 공급자는 비 차단 공급자로 대체됩니다.
아래를 참조하십시오.
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();
}
}
때로는 단순한 " 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은 정적 {} -블록을 실행합니다 . 그것이 전체 비밀입니다 : 정적 블록은 한 번만 호출되며, 주어진 패키지의 주어진 클래스 (이름)가이 하나의 클래스 로더에 의해로드 될 때입니다.
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를 호출하는 경우 경쟁 조건을 피했습니다.
'developer tip' 카테고리의 다른 글
PHP에서 현재 날짜와 시간을 어떻게 얻습니까? (0) | 2020.09.29 |
---|---|
JavaScript 콘솔의 색상 (0) | 2020.09.29 |
배열의 모든 멤버를 동일한 값으로 초기화하는 방법은 무엇입니까? (0) | 2020.09.28 |
stdout이 아닌 stderr을 파이프하는 방법은 무엇입니까? (0) | 2020.09.28 |
Android에서 startActivityForResult를 관리하는 방법은 무엇입니까? (0) | 2020.09.28 |