Java에서 싱글 톤 패턴을 구현하는 효율적인 방법은 무엇입니까?
Java에서 싱글 톤 패턴을 구현하는 효율적인 방법은 무엇입니까?
답변:
열거 형을 사용하십시오.
public enum Foo {
INSTANCE;
}
Joshua Bloch는 Google I / O 2008에서의 효과적인 Java Reloaded 강연에서 비디오에 대한 링크 에서이 접근 방식을 설명했습니다 . 그의 프레젠테이션의 슬라이드 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)); } }
편집 : "유효한 Java" 의 온라인 부분 은 다음과 같이 말합니다.
"이 접근법은 더 간결하고 직렬화 기계를 무료로 제공하며 정교한 직렬화 또는 리플렉션 공격에도 불구하고 다중 인스턴스화에 대한 확실한 보증을 제공한다는 점을 제외하고는 공공 현장 접근법과 기능적으로 동일합니다. 아직 널리 채택되지는 않았지만, 단일 요소 열거 형이 싱글 톤을 구현하는 가장 좋은 방법 입니다. "
사용법에 따라 몇 가지 "올바른"답변이 있습니다.
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
키워드를 사용하여 최종 사용자에게 알 렸습니다. 그런 다음 사용자가 자신의 Foo를 만들지 못하도록 생성자를 비공개로 만들어야합니다. 생성자에서 예외를 throw하면 사용자가 리플렉션을 사용하여 두 번째 Foo를 만들 수 없습니다. 그런 다음 private static final Foo
유일한 인스턴스를 보유 할 필드와 public static Foo getInstance()
이를 리턴 하는 메소드를 작성하십시오. Java 스펙은 생성자가 클래스를 처음 사용할 때만 호출되도록합니다.
객체가 크거나 구성 코드가 많고 인스턴스가 필요하기 전에 사용할 수있는 다른 액세스 가능한 정적 메서드 나 필드가있는 경우에만 지연 초기화를 사용해야합니다.
a 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가 실제로 사용하는 경우에만 실행이 게으른 인스턴스을 담당하고,이 스레드 안전을 보장한다.
객체를 직렬화하려면 직렬화 해제로 인해 사본이 생성되지 않아야합니다.
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. Lazy loading
2. Early loading
게으른 로딩은 비트 오버 헤드 (많은 솔직함)를 추가하므로 매우 큰 객체 또는 무거운 구성 코드가 있고 인스턴스가 필요하기 전에 사용할 수있는 다른 액세스 가능한 정적 메소드 또는 필드가있는 경우에만 사용하십시오 초기 로딩을 선택하는 것이 좋습니다.
싱글 톤을 구현하는 가장 간단한 방법은
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;
}
}
이것을 "이중 확인 잠금 관용구"라고합니다. 변덕스러운 진술을 잊기 쉽고 그것이 왜 필요한지 이해하기 어렵다.
자세한 내용은 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
이제 우리는 사악한 스레드에 대해 확신하지만 잔인한 직렬화는 어떻습니까? de-serialization이 새 개체를 만들지 않아도 확인해야합니다.
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 싱글 톤은 느리게 초기화 됩니다.
이 방법은보다 간결하고 직렬화 기계를 무료로 제공하며 정교한 직렬화 또는 리플렉션 공격에도 불구하고 다중 인스턴스화에 대한 확실한 보장을 제공한다는 점을 제외하고는 공공 현장 접근 방식과 기능적으로 동일합니다. 이 방법은 아직 널리 채택되지 않았지만 단일 요소 열거 형 유형이 단일 톤을 구현하는 가장 좋은 방법입니다.
"유효한 자바"의 여호수아 블로흐
이제 왜 ENUMS가 Singleton을 구현하는 가장 좋은 방법으로 여겨지고 인내심을 가져 주셔서 감사합니다 :)
내 블로그 에서 업데이트했습니다 .
serialVersionUID
입니다 0L
. 세 번째 문제점 : 사용자 정의 없음 : 직렬화 및 역 직렬화 중에 열거 유형으로 정의 된 클래스 별 writeObject, readObject, readObjectNoData, writeReplace 및 readResolve 메소드가 무시됩니다.
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;
}
}
getBar()
. (그리고 getBar
"너무 이른"이라고 불린다면 싱글 온이 어떻게 구현 되든지 동일한 문제에 직면하게 될 것입니다.) 위의 코드가
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에 대한 나의 대답은 (가장 간단한 샘플 코드를 제외하고) 다음과 같습니다.
이 접근 방식은 싱글 톤을 사용할지 여부를 쉽게 되돌릴 수있는 구현 세부 사항 (물론 스레드 안전성이 제공되는 경우)으로 분리 된 (따라서 유연하고 테스트 가능한) 아키텍처를 제공합니다.
TicketNumberer
단일 글로벌 인스턴스가 필요한 TicketIssuer
코드와 코드 줄이 포함 된 클래스를 작성하려는 위치를 고려하십시오 int ticketNumber = ticketNumberer.nextTicketNumber();
. 전통적인 싱글 톤 사고에서 이전 코드 줄은 다음과 같아야 TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
합니다. DI 사고에서 클래스에는와 같은 생성자가 있습니다 public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.
main
메소드 (또는 그 하수인 중 하나)가 종속성을 생성 한 다음 생성자를 호출 하기 때문에 수작업으로 제작 된 DI 아키텍처가이를 수행합니다 . 기본적으로 전역 변수 (또는 전역 방법)의 사용은 단순한 형태의 끔찍한 서비스 로케이터 패턴 이며, 해당 패턴의 다른 용도와 마찬가지로 의존성 주입으로 대체 될 수 있습니다.
작성하기 전에 왜 싱글 톤이 필요한지 실제로 고려하십시오. Java로 Google 싱글 톤을 사용하면 쉽게 넘어 질 수있는 그것들을 사용하는 것에 대한 준 종교적 논쟁이 있습니다.
개인적으로 나는 여러 가지 이유로 가능한 한 자주 싱글 톤을 피하려고 노력하며, 대부분은 인터넷 검색 싱글 톤으로 찾을 수 있습니다. 모든 사람들이 이해하기 쉽기 때문에 싱글 톤이 남용되는 경우가 많으며, "글로벌"데이터를 OO 디자인으로 가져 오는 메커니즘으로 사용되며 객체 수명주기 관리를 우회하기 쉽기 때문에 사용됩니다. 실제로 B 내부에서 A를 수행하는 방법에 대해 생각합니다. 중간 정도의 제어를 위해 IoC (Inversion of Control) 또는 DI (Dependency Injection)와 같은 것을 살펴보십시오.
정말로 필요한 경우 wikipedia에는 싱글 톤의 적절한 구현에 대한 좋은 예가 있습니다.
다음은 3 가지 접근 방식입니다
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;
}
}
버전 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();
}
}
게으른 로딩을 원하고 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 메소드를 동기화하거나 모든 싱글 톤에 대한 레지스트리를 만들 수도 있습니다.
volatile
나는 Enum singleton이라고 말할 것입니다
Java에서 enum을 사용하는 싱글 톤은 일반적으로 enum 싱글 톤을 선언하는 방법입니다. 열거 형 싱글 톤은 인스턴스 변수와 인스턴스 메소드를 포함 할 수 있습니다. 간단하게하기 위해 인스턴스 메소드를 사용하는 경우 해당 메소드의 스레드 안전성을 보장해야하는 것보다 오브젝트의 상태에 영향을 미치는 경우가 있습니다.
열거 형을 사용하면 구현하기가 쉽고 직렬화 가능한 객체와 관련하여 단점이 없으며 다른 방법으로 우회해야합니다.
/**
* 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
. 전송 된 데이터 유형에 변동이 없으므로 열거 형 유형에 대한 직렬화 가능 필드 및 데이터를 문서화 할 필요가 없습니다.
기존의 싱글 톤의 또 다른 문제점은 Serializable
인터페이스 를 구현 한 후에는 readObject()
메소드가 항상 Java의 생성자와 같은 새 인스턴스를 리턴 하기 때문에 더 이상 싱글 톤으로 남아 있지 않다는 것 입니다. readResolve()
아래와 같이 싱글 톤으로 교체하여 새로 생성 된 인스턴스를 사용 하고 삭제 하면이를 피할 수 있습니다.
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
일시적으로 만들어야 할 때 싱글 톤 클래스가 상태를 유지하는 경우 훨씬 더 복잡해질 수 있지만 Enum Singleton에서는 직렬화가 JVM에 의해 보장됩니다.
잘 읽다
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");
}
}
이것에 대해 게임에 약간 늦을 수도 있지만 싱글 톤을 구현하는 데 많은 뉘앙스가 있습니다. 홀더 패턴은 여러 상황에서 사용할 수 없습니다. 휘발성을 사용할 때 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 멤버가 널로 표시됩니다. 이로 인해 각 스레드는 새 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 () 메소드에 도달하고 모두 INSTANCE 멤버가 널로 표시됩니다. 그런 다음 동기화 된 블록에 도달하여 잠금을 획득하고 인스턴스를 작성합니다. 해당 스레드가 블록을 종료하면 다른 스레드가 잠금을 위해 경쟁하고 각 스레드가 하나씩 블록을 통과하여 클래스의 새 인스턴스를 만듭니다. 그래서 우리는 시작한 곳으로 돌아 왔습니다.
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 멤버에서 작업 할 때마다 성능이 저하됩니다. 존재 (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;
}
성능 적중은 휘발성 멤버에서 직접 작동하기 때문에 로컬 변수를 휘발성 값으로 설정하고 대신 로컬 변수에서 작동합시다. 이렇게하면 휘발성에 대한 작업 횟수가 줄어들어 손실 된 성능 중 일부가 회복됩니다. 동기화 된 블록에 들어갈 때 로컬 변수를 다시 설정해야합니다. 이렇게하면 잠금을 기다리는 동안 발생한 모든 변경 사항이 최신 상태로 유지됩니다.
최근에 이것에 관한 기사를 썼습니다. 싱글 톤 해체 . 이 예에 대한 자세한 정보와 "홀더"패턴의 예를 찾을 수 있습니다. 이중 점검 휘발성 접근 방식을 보여주는 실제 사례도 있습니다. 도움이 되었기를 바랍니다.
class MySingleton
어쩌면 – 아마도 그럴까요 final
?
BearerToken
인스턴스가 BearerTokenFactory
특정 권한 서버로 구성된-의 일부이므로 정적이 아닙니다 . 많은 BearerTokenFactory
개체 가있을 수 있습니다. 각 개체 에는 자체 "캐시 된"개체 BearerToken
가 있으며 만료 될 때까지 배포 할 수 있습니다. 의 hasExpired()
메소드 는 만료 된 토큰을 전달하지 않도록 BeraerToken
팩토리 get()
메소드 에서 호출됩니다 . 만료되면 인증 서버에서 새 토큰이 요청됩니다. 코드 블록 다음에 나오는 단락에서이를 자세히 설명합니다.
다음은 간단한 구현 방법입니다 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();
}
}
getInstance()
. 그러나 실제로 클래스에 다른 정적 메소드 가없고 Singleton
호출 getInstance()
만하면 실제 차이는 없습니다.
당신은 필요가 두 번 확인 관용구를 사용하면 게으르게 클래스의 인스턴스 변수를로드해야합니다. 정적 변수 또는 싱글 톤을 느리게로드해야하는 경우 주문형 홀더 관용구를 초기화 해야합니다 .
또한 싱글 톤이 서비스 가능해야하는 경우 다른 모든 필드는 일시적이어야하며 싱글 톤 객체를 불변으로 유지하려면 readResolve () 메소드를 구현해야합니다. 그렇지 않으면 객체가 직렬화 해제 될 때마다 객체의 새 인스턴스가 작성됩니다. readResolve ()가하는 것은 readObject ()가 읽은 새로운 객체를 대체하는 것인데,이 객체를 참조하는 변수가 없기 때문에 새로운 객체를 가비지 수집해야합니다.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
열거 형 싱글 톤
스레드로부터 안전한 싱글 톤을 구현하는 가장 간단한 방법은 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");
}
}
싱글 톤에 대해 종종 사용되는 또 다른 주장은 테스트 가능성 문제입니다. 싱글 톤은 테스트 목적으로 쉽게 조롱 할 수 없습니다. 이것이 문제로 판명되면 다음과 같이 약간 수정하고 싶습니다.
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;
}
}
이 게시물을 살펴보십시오.
가장 좋은 답변의 "Singleton"섹션에서
싱글 톤 (매번 동일한 인스턴스 (보통 자체)을 리턴하는 작성 메소드로 인식 가능)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
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();
}
}
때로는 간단한 " 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를 호출하는 경우 경쟁 조건을 피했습니다.