싱글톤 패턴은 객체의 인스턴스가 오직 1개만 생성 되는 패턴 이다.
시스템 런타임, 환경세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다. 인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.
예를 들어, 설정 화면은 딱 하나의 인스턴스에서만 변경할 수 있게 해야한다. 어떤 설정에서는 엔터가 저장이고, 다른 설정에서는 클릭이 저장이면 헷갈릴 것이다. 이러한 경우 처럼 딱 하나만 필요한 경우가 존재하기 때문에 싱클톤 패턴이 존재한다.
싱글톤 패턴을 사용하는 이유
- 메모리 측면 (한번의 new를 통해 고정된 메모리 영역을 사용) → 속도 향상의 이점
- 데이터 공유가 쉽다 (싱글톤 인스턴스가 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들에서 접근하여 사용 가능)
private생성자와 static메소드를 사용하여 구현
- private 생성자를 만들어주면 new를 사용할 수 없게 된다 (클래스 밖에서 사용하고자 하면 컴파일 에러가 난다)
- static 메소드를 생성하여 클래스 밖에서 인스턴스에 접근할 수 있게 만들어준다.
public class Settings {
private static Settings instance;
private Settings() {} //생성자를 private으로 선언
public static Settings getInstance() { // static method
if(instance == null) { // instance가 만들어진 적이 없다면
instance = new Settings();
}
return instance;
}
}
public class App {
public static void main(String[] args){
Settings settings = Settings.getInstance(); // 사용
}
}
발생하는 문제 :
멀티쓰레드 환경에서는 안전하지 않다. (ThreadSafe 하지 않다)
두 개의 쓰레드가 getInstance 메소드를 거의 동시에 사용한다고 가정해보자.
첫 번째 쓰레드가 if (instance == null) 문을 타고 아직 instance 를 생성하기 전일 때,
두 번째 쓰레드가 if (instance == null) 문에 또 들어올 수 있다. 즉, new 가 다중 할당 될 가능성이 있다.
- 이런 일은 쓰레드가 만드는 순서에 상관없이 거의 동시에 실행되기 때문이다.
해결 방법 (ThreadSafe)
- Synchronized 키워드
- getInstance 메소드를 호출할 때마다 동기화를 처리하는 작업때문에 성능의 저하가 생길수있다는 단점이 있다. (동기화라는 메커니즘의 자체 특성이라 어쩔 수 없음)
public static synchronized Settings getInstance() {
if(instance == null) {
instance = new Settings();
}
return instance;
}
- 이른 초기화(Eager initialization)
- 멀티쓰레드 환경에서 안전하지만, 객체를 미리 생성하는 것이 단점 (애플리케이션에서 쓰질 않으면 로딩할 때 쓴 리소스가 낭비니까)
public class Settings {
private static final Settings INSTANCE = new Settings();
private Settings() {}
public static Settings getInstance() {
return INSTANCE;
}
}
- Double checked locking
- volatile 키워드 필요 (java 1.5 이상)
- 이미 인스턴스가 있는 경우에는 synchronized가 쓰이지 않음. 그러나 모든 쓰레드가 synchronized가 쓰이는 1번 방법과 달리, if 문 안에 들어오는 경우 (= 멀티쓰레드가 활발히 일어나는 경우)에만 synchronized가 쓰이기 때문에, 훨씬 성능에 유리하다
- 인스턴스를 필요한 시점에 만들 수 있다
public class Settings {
private static volatile Settings instance;
private Settings() {}
public static Settings getInstance() {
if(instance == null) {
synchronized (Settings.class) { // Settings 의 class 를 lock 으로 사용 - check 1
if(instance == null) { // - check 2
instance = new Settings();
}
}
}
return instance;
}
}
- Static inner 클래스 → 권장
- getInstance 메소드가 호출될 때 SettingHolder 클래스가 로딩이 되고, 그 때 인스턴스를 만들기 때문에 LazyLoading이 가능
- Double checked locking 보다 이론적으로 간단
LazyLoading ?
페이지를 읽어들이는 시점에 중요하지 않은 리소스 로딩을 추후에 하는 기술
대신 중요하지 않은 리소스들은 필요할 때 로드가 되어야함
public class Settings {
private Settings() {}
private static class SettingsHolder {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance() {
return SettingHolder.INSTANCE;
}
}
실무에서는 어떻게 쓰이나?
- 자바 java.lang.Runtime : Runtime 의 경우 getRuntime 을 통해서만 가져올 수 있고, new 를 이용해 생성하지 못한다.)
- Spring에서 싱글톤 스코프 로 Bean 을 클래스 안에 등록해서 사용 (ApplicationContext 안에서 유일한 인스턴스로 관리를 해주는 것이고, 사실 엄밀히 따지면 싱글톤 패턴은 아님) → 싱글톤 패턴은 안티패턴으로 불릴만큼 단독으로 사용한다면 객체 지향에 위반되는 사례가 많음. 따라서 이러한 문제점을 보완하면서 장점의 혜택을 누리기 위해 싱글턴 컨테이너의 도움을 받아 싱글톤 스콥으로 관리
- 다른 디자인 패턴(빌더, 퍼사드, 추상팩토리 등) 구현체의 일부로 쓰이기도 한다.
싱글톤 패턴의 문제점
- 테스트가 어렵다. 싱글톤 인스턴스는 자원을 공유하기 때문에 테스트가 격리된 환경에서 수행되려면 매번 인스턴스를 초기화해야한다. (초기화를 하지 않을 시, 상태가 전역에 공유되기 때문에 온전한 테스트를 수행하기 어려워진다)
- 싱글톤은 사용하는 모든 추상화에 제한을 가한다 → DIP(의존성 역전 원칙)위반
- 객체지향의 의도와 맞지 않는다 →private생성자를 가지고 있기 때문에 상속할 수 없다(다형성 특징을 적용할 수 없다)
[출처]
'𝑷𝒓𝒐𝒈𝒓𝒂𝒎𝒎𝒊𝒏𝒈 > 𝐽𝐴𝑉𝐴' 카테고리의 다른 글
[JAVA] Collection - List (ArrayList) (0) | 2022.11.21 |
---|---|
[디자인패턴] 프록시(Proxy) 패턴 (0) | 2022.11.19 |
[JAVA] Thread (스레드) (0) | 2022.11.02 |
[JAVA] 자바 queue (큐) 클래스 (0) | 2022.02.13 |
[JAVA] 자바 stack (스택) 클래스 (0) | 2022.02.10 |