싱글톤 (Singleton) 패턴
싱글톤 (Singleton) 패턴
인스턴스를 오직 한개만 제공하는 클래스
시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다. 인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.
1. private 생성자에 static 메소드 사용
싱글톤 패턴을 가장 단순히 구현하는 방법
new를 사용해서 인스턴스를 만들 수 있도록 하면 안된다.
→ private 생성자로 만든다.
Settings 클래스 내에 글로벌하게 인스턴스를 획득할 수 있는 메서드를 만들어준다.
여기서 문제점은 getInstance를 할 때마다 매번 새로운 객체를 생성해서 반환한다.
그렇다면 호출하는 곳에서 매번 다른 인스턴스를 얻게되고 하나의 인스턴스가 아니게 된다.
클래스 내에 인스턴스를 내부 변수로 선언해서 사용하도록 한다.
단점
쓰레드 세이프 하지 않은 방법이다.
예) 빨간색 스레드가 if문을 지나 new를 해서 빠져나가기 전에 파란색 스레드 또한 if문을 통과해 new를 호출하려고 하는 경우 두 개의 인스턴스가 생기게 된다.
생성자를 private으로 만든 이유?
getInstance() 메소드를 static으로 선언한 이유?
getInstance()가 멀티쓰레드 환경에서 안전하지 않은 이유?
멀티 쓰레드 환경에서 안전하게 구현하는 방법
1. 동기화(synchronized)를 사용
가장 쉬운 방법은 메서드를 동기화 시키는 방법이다.
메서드에 한 번에 하나의 스레드만 들어올 수 있도록 처리한다.
멀티스레드 환경에서 하나의 인스턴스만 보장할 수 있다.
단점
getInstance()를 호출할 때마다 "동기화"를 처리하기 위한 작업 때문에 성능에 불이익이 생길 수 있다.
동기화 매커니즘이 lock을 잡아서 lock을 가지고 있는 스레드만 이 영역에 접근할 수 있기 때문이다. 이런 매커니즘을 처리하기 위한 과정이 필요하기 때문에 부가적인 성능 부하가 생길 수 있다.
자바의 동기화 블럭 처리 방법은?
getInstance() 메소드 동기화시 사용하는 락(lock)은 인스턴스의 락인가 클래스의 락인가? 그 이유는?
2. 이른 초기화 (eager initialization)을 사용
객체를 나중에 만들지 않아도 된다면, 미리 만들어두고 사용하면 된다.
(+) final 키워드를 붙여서 변하지 않도록 해준다.
getInstance()를 여러 쓰레드에서 동시에 접근해도 이미 만들어둔 인스턴스를 반환하기만 하므로 쓰레드 세이프 하다.
인스턴스는 클래스가 로딩되는 시점에 static 변수들이 초기화된다.
단점
미리 만들어 둔다는 것 자체가 단점이 될 수 있다.
인스턴스를 만드는 과정이 오래걸리고 메모리를 많이 사용한다면 만들어놓고 사용지않는 경우
애플리케이션 로딩 시점에 많은 리소스를 써서 만들어 뒀음에도 불구하고 안쓰게 되는 것이다.
이른 초기화가 단점이 될 수도 있는 이유?
만약에 생성자에서 checked 예외를 던진다면 이 코드를 어떻게 변경해야 할까요?
3. double checked locking으로 효율적인 동기화 블럭 만들기
추후 사용될 시점에 인스턴스를 만들면서도 동기화 처리의 비효율을 없애는 방법
먼저 체크를 한번 한 뒤에, synchronized 블럭 내에서 한번 더 체크를 하는 방식 → "double checked"
volatile
키워드를 써줘야 java 1.5 이상 부터 동작하는 double checked locking 기법이 된다.
장점
인스턴스를 필요로 하는 시점에 만들 수 있다.
getInstance()를 호출할 때마다 매번 동기화가 걸리는 것이 아니라 인스턴스가 만들어지지 않은 경우에만 동기화시키기 때문에 효율적이다.
단점
복잡한 방식이다. 왜
volatile
키워드를 쓰는지 이해하려면 java 1.4 이하가 멀티스레드 환경에서 메모리를 다루는 방법까지 이해해야 한다.이 방식은 java 1.5부터 동작한다.
double check locking이라고 부르는 이유?
instacne 변수는 어떻게 정의해야 하는가? 그 이유는?
4. static inner 클래스를 사용
권장하는 방법 중 하나이다.
필드 자체가 필요없다.
lazy하게 만들면서
volatile
키워드는 필요없다.
private static inner class를 만든다.
장점
멀티스레드 환경에서 안전
getInstance()가 호출될 때,
Settings4Holder
클래스가 로딩이 되고, 그 때INSTANCE
를 만들기 때문에 lazy 로딩도 가능한 코드이다.
이 방법은 static final를 썼는데도 왜 지연 초기화 (lazy initialization)라고 볼 수 있는가?
★ 하지만 지금까지 살펴본 방법들을 깨트릴 수 있는 코딩 방법들이 존재한다.
싱글톤 (Singleton) 패턴 구현 깨트리는 방법
1. 리플렉션을 사용한다면?
정상적으로 싱글톤을 사용한다면 getInstance()
로 만들어놓은 인스턴스를 가져다 사용하는 방법밖에 없다.
하지만, 리플렉션을 사용하면 새로운 인스턴스를 만들 수 있다.
setAccessible(true)
를 이용해서 private으로 만든 생성자에 접근할 수 있다.
리플렉션에 대해 설명하세요.
setAccessible(true)를 사용하는 이유는?
2. 직렬화 & 역직렬화를 사용한다면?
직렬화 : Object → File형태로 저장 & 네트워크 전송을 위한 형태로 변환
역직렬화 : File이나 네트워크 전송으로 전달받은 데이터를 Object로 변환
자바에서 역/직렬화를 위해 Serializable
인터페이스를 구현(implement)할 수 있다.
그말인 즉슨, 이 객체를 File로 저장해뒀다가 다시 로딩할 수 있다는 의미이다.
역직렬화를 하면, 반드시 생성자를 사용해서 다시 한번 인스턴스를 만들어주게 된다.
역직렬화 대응 방안
readResolve()
라는 메서드가 있다. (Serializable에 명시적으로 이 메서드가 정의되어있지는 않다.)이 시그니처 (
protected Object readResolve()
)를 가지고 있으면 역직렬화시 이 메서드를 반드시 사용하게 된다.(참고 - 이 방법으로는 리플랙션을 방지할 수는 없다.)
자바의 직렬화 & 역직렬화에 대해 설명하세요.
SerializableId란 무엇이며 왜 쓰는가?
try-resource 블럭에 대해 설명하세요.
5. enum을 사용
enum을 이용해서 property와 method를 정의할 수 있기 때문에 얼마든지 사용가능하다.
장점
리플렉션에 안전하다.
바이트코드를 확인해보면 해당 enum에 String 하나를 인자로 받는 생성자가 하나 있는 것을 확인할 수 있다. 이를 리플렉션을 사용해서 생성자를 호출할 수 있지 않을까? → 안된다.
enum은 유일하게 리플렉션으로 인스턴스를 만들 수 없도록 막아놨다.
역/직렬화에 안전하다.
enum은 기본적으로 Serializable
을 구현하고 있다. (Enum클래스를 상속받기 때문에 이를 확인해보면 된다.)
enum의 역/직렬화는 개발자가 별다른 조치를 취하지않아도 안전하게 동일한 인스턴스로 역직렬화가 된다.
단점
클래스를 로딩하는 순간 미리 만들어진다.
이것이 크게 문제가 되지 않는다면 가장 안전한 방법이다.
상속은 쓰지 못한다.
enum은 오로지 ENUM만 상속받을 수 있다. (그것도 컴파일할 때 상속받는 것이지 명시적으로 선언하는 것이 아니다.)
싱글톤으로 만들어야하는 클래스가 특정 클래스를 상속받아서 만들어야 한다면, static inner 클래스(Holder)를 사용하는 방법을 써야한다.
Lazy Loading 까지 써야한다면, static inner 클래스(Holder)를 사용하는 방법을 쓰는 것이 무난하다.
enum 타입의 인스턴스를 리팩토링을 만들 수 있는가?
enum으로 싱글톤 타입을 구현할 때의 단점은?
직렬화 & 역직렬화 시에 별도로 구현해야 하는 메소드가 있는가?
복습
자바에서 enum을 사용하지 않고 싱글톤 패턴을 구현하는 방법은?
private 생성자와 static 메소드를 사용하는 방법의 단점은?
enum을 사용해 싱글톤 패턴을 구현하는 방법의 장점과 단점은?
static inner 클래스를 사용해 싱글톤 패턴을 구현하라.
실무 사용 예
1. 스프링에서 빈의 스코프 중에 하나로 싱글톤 스코프
SpringConfig라는 설정파일을 만들고 해당 설정파일로 ApplicationContext를 만들면 여기서 getBean()
으로 가져오는 인스턴스는 항상 같다. (기본이 싱글톤 스코프이기 때문에)
SpringConfig라는 빈을 정의
싱글톤 스코프와 싱글톤 패턴은 다르지만, 인스턴스를 ApplicationContext 내부에서 유일한 인스턴스로 관리해주는 것이다.
엄밀히 따지면, String은 싱글톤이 아니고, 얼마든지 String을 여러개의 다른 인스턴스를 만들 수 있다.
즉, 싱글톤 패턴이 쓰인 것은 아니지만 실제로 업무를 할 때 유일한 객체가 필요한 경우, 스프링을 사용하고 있다면 '싱글톤 스코프'로 빈을 등록해서 사용하는 경우가 많다.
2. 자바 java.lang.Runtime
Runtime.getRuntime()
을 통해서만 인스턴스를 얻을 수 있다. (new 키워드 사용불가)
애플리케이션이 실해되고 있는 환경, 문맥정보에 대한 것을 얻을 수 있다.
ex) 메모리 정보
3. 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.
※ Intellij에서 ByteCode 보는 방법
shift + shift → 'show bytecode' 검색
[view] > [show bytecode]
Last updated