-
[디자인패턴] 싱글톤패턴을 쓰는 이유와 주의할 점Computer Basis/디자인패턴 및 설계이론 2022. 3. 15. 13:08
싱글톤패턴
애플리케이션 시작 시 어떤 클래스가 최초 한번만 메모리를 할당하고 그 메모리에 인스턴스를 생성하는 디자인 패턴
그리고 계속 그 하나의 인스턴스로 작업을 처리한다.싱글톤 패턴을 쓰는 이유
고정된 메모리 영역(데이터영역)에 한번의 new로 인스턴스를 할당하여 사용하기 때문에 메모리 효율성 증가
전역 인스턴스의 특성상 어디서나 접근가능하기에 데이터 공유에 용이
인스턴스가 절대적으로 한개만 존재하는 것을 보증하고 싶을 경우 사용한다.
(멀티쓰레드 환경에서 두개이상이 생겨날 수 있는데 이는 싱글톤 패턴이 아니다.)
이미 생성된 객체이므로 객체로딩시간이 줄어들어 성능이 좋아지는 장점도 있다.싱글톤 패턴 사용 시 주의할 점
싱글톤 객체가 여기저기서 너무 많이 참조된다면 다른 클래스들 간의 결합도가 같이 증가하게 된다.
-> (개방-폐쇄 원칙, Open-Closed Principle) 의 위배
-> 리팩토링이 어려워짐위의 그림에서 클래스 A가 싱글톤 객체의 내부데이터에 변경을 가하면 추후 C,B는 변경된 데이터로 작업을 하게 된다.
이렇듯, 싱글톤 객체가 공유하는 데이터에 대해선 주의가 필요하다.멀티쓰레드 환경에서도 한개의 객체를 유지하는 방법
1. Thread safe Lazy Initialization
private 생성자로 외부생성을 막으며, synchronized 키워드로 thread-safe 하다.
하지만 synchronized 는 비교적 큰 성능저하가 발생되므로 권장하지 않는 방법이라 한다.
public class IronMan{ private static IronMan instance; private IronMan(){} //private 접근제어자로 외부에서의 new 키워드를 막는다. public static synchronized IronMan getInstance(){ if(instance == null){ instance = new IronMan(); } return instance; } }
2. Thread safe Lazy Initialization + Double-Checked Locking
멀티쓰레드 환경에서 인스턴스가 null 이면 synchronized 키워드가 작동하는 방식이라 1번보다 아주 조금 완화된 버전
public class IronMan{ private static volatile IronMan instance; private IronMan(){} public static IronMan getInstance(){ if(instance == null){ synchronized (IronMan.class){ if(instance == null) instance = new IronMan(); } } return instance; } }
하지만 트래픽이 많아서 쓰레드가 정말 많고
해당 인스턴스에 접근하는 횟수가 많다면
synchronized 키워드는 거의 계속 작동하게 된다.
volatile 같은 경우는 jdk 1.5 이후의 문법임
3. Thread safe Eager Initialization
public class IronMan{ private static final IronMan INSTANCE = new IronMan(); private IronMan(){} public static IronMan getInstance(){ return INSTANCE; } }
미리 만들어두고 쓰는(Eager) 방식이다.
때문에 인스턴스생성비용이 엄청 큰 인스턴스이지만
안쓰일 수도 있는 경우에는 해당방식을 권장하지 않는다.
4. Initialization on demand holder idiom
클래스안에 클래스(Holder)를 두어 JVM Class loader 매커니즘과 Class가 로드되는 시점을 이용한 방법
Holder안의 인스턴스가 static이기 때문에 클래스로딩(getInstance 호출시점)시점에 한번만 호출되며
final을 사용하여 재할당을 금지하는 방법
가장 많이 사용되는 일반적인 기법
이라고 한다.public class IronMan{ private IronMan(){} private static class IronManHolder{ private static final Ironman INSTANCE = new Ironman(); } public static IronMan getInstance(){ return IronManHolder.INSTANCE; } }
위의 4가지 싱글톤패턴을 부수는 방법
1. 리플렉션
public static void main(String[] args) { IronMan ironMan1 = IronMan.getInstance(); Constructor<Settings> constructor = IronMan.class.getDeclaredConstructor(); constructor.setAccessible(true); IronMan ironMan2 = constructor.newInstance(); System.out.println(ironMan1 == ironMan2); //false }
2. 직렬화&역직렬화
역직렬화 시 readResolve() 호출에 new 생성자가 반드시 호출되는 것을 활용
public static void main(String[] args) { IronMan ironMan1 = IronMan.getInstance(); IronMan ironMan2 = null; try(ObjectOutput output = new ObjectOutputStream(new FileOutputStream("ironMan.obj"))){ output.writeObject(ironMan1); } try(ObjectInput input = new ObjectInputStream(new FileInputStream("ironMan.obj"))){ ironMan2 = (IronMan) input.readObject(); } System.out.println(ironMan1 == ironMan2); //false }
역직렬화 대응방안
public class IronMan implements Serializable{ private IronMan(){} private static class IronManHolder{ private static final Ironman INSTANCE = new Ironman(); } public static IronMan getInstance(){ return IronManHolder.INSTANCE; } // Override 같은 느낌 // 구현되지 않으면 역직렬화 디폴트 매커니즘에 따라 return new IronMan(); 이 호출됨 protected Object readResolve(){ return getInstance(); } }
리플렉션 + 직렬화&역직렬화 대응방안
enum 으로 싱글톤 구현하면 된다.
enum 은 자바에서 리플렉션으로 newInstance 를 할 수 없도록 막아 놓음.
enum 은 Serializable 을 구현하고 있어 직렬화&역직렬화에서도 안전하다.public enum IronMan{ INSTANCE; /* other function or constructor */ }
하지만 enum 은 클래스 로딩순간 이미 만들어지며
상속을 사용하지 못한다.
enum 은 인터페이스를 implements 할 수는 있다. (컴파일 시점에만)
그래서 스프링데이터처럼 프록시를 실행시점에 만들어내지 못한다. (이건 아직 검증 중)
Java & Spring 에서의 싱글톤
Reference
1. 싱글톤 패턴 관련 블로그
728x90'Computer Basis > 디자인패턴 및 설계이론' 카테고리의 다른 글
[디자인패턴] 브릿지 패턴 (Bridge Pattern) (0) 2022.05.08 [설계이론] Project Structure - 프로젝트 구성을 어떻게 설계해야 할까? (패키지 분리기준) (0) 2022.03.27 [디자인패턴] 상속보다는 컴포지션을 사용하자!!! (0) 2022.02.17 [설계이론] 마이크로서비스 아키텍처(MSA)는 언제 고려해야하는가? (0) 2022.02.03 [디자인패턴] 템플릿패턴(Template Pattern) (0) 2022.01.26