싱글톤 패턴(Singleton Pattern)
인스턴스가 하나뿐인 특별한 객체
싱글톤 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공합니다.
다른 클래스에서는 싱글톤으로 구현한 클래스 인스턴스를 만들지 못하도록 해야합니다.
싱글톤 패턴을 실제로 적용할 때는 클래스에서 하나뿐인 인스턴스를 관리하도록 만들면 됩니다.
그리고 다른 클래스에서도 자신의 인스턴스를 추가로 만들지 못하게 해야합니다.
이말은 인스턴스가 필요하다면 반드시 클래스 자신을 거치도록해야함을 의미합니다.
어디서든 해당 인스턴스에 접근할 수 있도록 전역 접근 지점을 제공합니다.
언제든 인스턴스가 필요하면 클래스에 요청할 수 있게 만들어 놓고, 요청이 들어오면 하나뿐인 인스턴스를 건네주도록 만들어야 합니다.
여기서 싱글톤은 게으른 방식으로 생성되도록 구현할 수도 있습니다.
이러한 방법은 자원을 많이 사용하는 곳에서 유용하겠죠?
싱글톤 패턴 구조
- uniqueInstance
- 싱글턴의 하나뿐인 인스턴스가 저장
- getInstance()
- 정적메소드(클래스 메소드)로써
Singleton.getInstance()
를 사용하면 어디서든 호출이 가능하다.
- 정적메소드(클래스 메소드)로써
싱글톤 패턴의 종류
고전적인 싱글톤(게으른 인스턴스 생성)
public class ClassicSingleton {
//정적 변수
private static ClassicSingleton uniqueInstance;
private ClassicSingleton() {
}
public static ClassicSingleton getInstance() {
if (uniqueInstance == null) {
//게으른 인스턴스 생성(LazyInstantiation)
uniqueInstance = new ClassicSingleton();
}
return uniqueInstance;
}
}
뭐야 엄청 간단하네?!
하지만, 멀티스레드 환경에서 이 방법에는 문제가 있습니다.
서로 다른 2개의 객체가 리턴되었습니다.. 😭
멀티스레딩 문제 해결 싱글톤
synchronized
속도가 중요하지 않다면 사용해도 상관 없습니다.getInstance()
를 동기화하는 게 어려운 일도 아니고, 효율도 좋을 수 있습니다.
다만 메소드를 동기화 하면 100배 정도 저하됩니다.
만약에, getInstance()
가 애플리케이션에서 병목으로 작용한다면 다른 방법을 생각해야 합니다..!
public class SynchronizedSingleton {
private static SynchronizedSingleton uniqueInstance;
private SynchronizedSingleton() {
}
/** 멀테스레딩 문제 해결
* 동기화할 때, 성능면에서 100배정도 느려진다.
* 처음을 제외하면 동기화는 불필요한 오버헤드만 증가시킬뿐..
** /
public static synchronized SynchronizedSingleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new SynchronizedSingleton();
}
return uniqueInstance;
}
}
static initializer
인스턴스가 필요할 때는 생성하지말고 처음부터 만듭니다.
처음부터 인스턴스를 만들면 좋습니다.
클래스가 로딩될 때, JVM
에서 Singleton
의 하나뿐인 인스턴스를 생성해줍니다.JVM
에서 하나뿐인 인스턴스를 생성하기 전까지 그 어떤 스레드도 uniqueInstance
정적 변수에 접근할 수 없습니다.
public class Singleton {
//정적 초기화 부분에서 인스턴스 생성, 이러면 멀티스레드 환경에서도 별문제가 없다.
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
DCL(Double-Checked Locking)
DCL
을 사용해서 getInstance()에서 동기화되는 부분을 줄입니다.DCL(Double-Checked Locking)
을 사용하면 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다. 이러면 처음에만 동기화하고 나중에는 동기화하지 않아도 됩니다…!
public class DCLSingleton {
//volatile는 Java 변수를 Main Memory에 저장
private volatile static DCLSingleton uniqueInstance;
/**
* DCL(Double-Checked Locking)을 사용하면
* 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다.
* 이러면 처음에만 동기화를 진행하고 나중에는 동기화 하지 않아도 됩니다.
*/
public static DCLSingleton getInstance() {
if(uniqueInstance == null) { //인스턴스가 있는지 확인하고, 없으면 동기화된 블록으로..
synchronized (DCLSingleton.class) {
if(uniqueInstance == null) {
uniqueInstance = new DCLSingleton();
}
}
}
return uniqueInstance;
}
}
하지만,,,,,,,, 위와 같은 방법들은 동기화
, 클래스 로딩
, 리플렉션
, 직렬화
, 역직렬화
문제들이 발생할 수 있습니다..
클래스 로더
- 클래스 로더 2개가 각기 다른 싱글턴의 인스턴스를 가지게 된다면?
- 클래스 로더마다 서로 다른 네임스페이스를 정의하기 때문에 클래스 로더가 2개 이상이라면 같은 클래스를 여러 번 로딩할 수도 있습니다. 그러니 싱글턴을 그런 식으로 로딩하면 인스턴스가 여러 개 만들어지는 문제가 발생합니다.이러한 경우에는 직접 클래스 로더를 지정하면 해결 가능합니다.
리플렉션(Reflection
), 직렬화(Serializable
), 역직렬화(Deserialzation
)
출처: JAVA Reflection & Serializable :: Mhwan’s Story
- 리플렉션
- 리플렉션은
run-time
에 동적으로 클래스 정보를 알아내고, 실행할 수 있는 것을 의미합니다.- 객체를 통해 그 객체의 원래 클래스와 그에 따르는 여러 정보들을 알아내는 방법
- 일반적인 객체 생성은
compile-time
에 우리가 작성한 클래스와 메소드가 컴파일되어JVM
의 메모리 영역에 올라와 있는데, 리플렉션은 그점이 아니라run-time
에 되는것이 가장 큰 차이점 입니다.- 동적으로 실행(동작 및 수정) 할 수 있게 되는 것을 의미 합니다.
IDE
에서 자동완성 기능이 이에 해당합니다.^^
- 리플렉션을 통해 클래스를 분석하고(객체의 생성자, 변수, 메소드 private 포함) 객체를 만들고, 메소드를 실행할 수도 있게 됩니다.
- 출처: 리플렉션(Reflection) -1
- 리플렉션은
- 직렬화
java
시스템 내부에서 사용되는 객체나 데이터를 외부의 자바 시스템에서 이용할 수 있도록byte
형태로 변환하는 기술 입니다.Serializable
을 이용할때는serialVersionUID
값을 설정하기를 권장 합니다.serialVersionUID
값은 해당 클래스의 버전이 서로 맞는지 체크하는 용도로 사용합니다.- 이를 설정하지 않으면
JVM
에서 자동으로 생성하는데,OS
에 따라JVM
이 달라지기 때문에 예외가 발생할수 있습니다.java
의 직렬화 대상은 동일한serialVersionUID
을 가지고 있어야 합니다.!!
- 이를 설정하지 않으면
- 역직렬화
- 외부의 자바 시스템의
byte
형태의 데이터를java
시스템 내부에서 사용되는 객체나 데이터로 변환하는 기술 입니다.
- 외부의 자바 시스템의
그러면 어떻게 사용하라는거야?!!
Enum
enum
을 사용하면 모든 문제를 해결하고 완벽한 싱글톤 패턴을 구현할 수 있습니다.
public enum EnumSingleton {
UNIQUE_INSTANCE;
}
public class SingletonClient {
public static void main(String[] args) {
EnumSingleton enumSingleton = EnumSingleton.UNIQUE_INSTANCE;
}
}
정리
- 클래스에 싱글톤 패턴을 적용하면 1개의 인스턴만 생성된다.
- 싱글톤 패턴의 인스턴스는 어디서든지 접근 가능하다.
java
의enum
을 사용하면 간단하게 구현할 수 있다.
'컴퓨터과학 > 0 + 소프트웨어 아키텍처(디자인 패턴)' 카테고리의 다른 글
[소프트웨어 아키텍처] 13. 프로토타입 패턴(Prototype Pattern -java) (0) | 2022.12.07 |
---|---|
[소프트웨어 아키텍처] 12. 빌더 패턴(Builder Pattern -java) (0) | 2022.12.06 |
[소프트웨어 아키텍처] 10. 중재자 패턴(Mediator Pattern -java) (0) | 2022.12.05 |
[소프트웨어 아키텍처] 9. 책임 연쇄 패턴(CoR: Chain of Responsibility Pattern -java) (0) | 2022.11.29 |
[소프트웨어 아키텍처] 8. 팩토리 메소드 패턴, 추상 팩토리 패턴(Factory Method Pattern, Abstract Factory Pattern -java) (0) | 2022.11.29 |