베하~!
BTC 블랙아웃입니다!!
이번 포스팅에서는 디자인 패턴 중에서도 생성 패턴에 속하는 싱글톤 패턴에 대해서 알아보겠습니다. 싱글톤 패턴이란 특정 클래스의 인스턴스가 프로그램 내에서 오직 하나만 존재하도록 보장하는 패턴입니다.
이는 주로 시스템 런타임, 환경 세팅에 대한 정보 등 인스턴스가 여러개일 때 문제가 생길 수 있는 경우에 사용합니다.
그럼 이제 이 싱글톤 패턴을 구현하는 여러 가지 방법에 대해 자세히 알아보겠습니다.
Private 생성자에 static 메서드
첫 번째 방법은 private 생성자를 통해 인스턴스 생성을 제한하고, static 메서드를 통해 인스턴스를 제공하는 방식입니다.
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
다만 이 코드가 멀티쓰레드 환경에 놓인다면 싱글톤이 깨질 수가 있습니다.
여러개의 쓰레드가 getInstance() 메서드를 동시에 호출하게 되면 instance가 null인지 체크하는 시점에 모두 true로 인식되고 쓰레드의 수만큼 new Singleton();을 호출하게 되기 때문입니다.
그러므로 멀티 쓰레드 환경에서 싱글톤 패턴을 구현하는 방법을 알아보겠습니다.
멀티 쓰레드 환경에서 싱글톤 구현
동기화(synchronized)
public class Singleton {
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
synchronized 키워드를 사용하여 getInstance() 메서드 전체를 동기화하여 멀티 쓰레드 환경에서도 싱글톤 인스턴스를 생성하도록 보장할 수 있습니다.
그러나 이 방법은 getInstance() 메서드의 모든 호출마다 동기화 처리 작업이 이루어지기 때문에 성능 저하가 발생할 수 있다는 단점이 있습니다.
동기화 처리 작업 : lock을 걸어서 하나의 쓰레드만 들어오고 다 끝난 후에 다시 lock을 풀어준다.
이른 초기화(eager initialization)
다음은 이른 초기화를 사용하는 방법입니다.
public class Singleton {
private static final Singleton INSTANCE = new Singleton ();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
이 방법은 인스턴스를 미리 만들어 두는 방법입니다. 이 방식은 미리 인스턴스를 생성해두므로 스레드에 안전하지만, 인스턴스를 미리 생성하므로 불필요한 리소스를 소모할 수 있다는 단점이 있습니다.
double checked locking
public class Singleton {
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
이 방법은 if를 통해 instance가 null인지 체크한 후 synchronized를 통해 제어하는 방법입니다.
여러개의 쓰레드가 들어오더라도 if 통과 후에 synchronized 블록에서 하나의 쓰레드가 들어가면 다른 쓰레드들은 대기하게 되기 때문에 싱글톤을 보장할 수 있습니다.
또한 위의 synchronized 키워드만 쓴 코드와 다르게 if 문에서 한번 체크하기 때문에 인스턴스가 이미 생성된 후로는 동기화 작업을 건너 뛰기 때문에 성능면에서 더욱 효율적입니다.
static inner 클래스
public class Singleton {
private Singleton() { }
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
이 방법은 내부 정적 클래스를 사용하는 방법이며, 이는 위 방법들보다 권장되는 방법이기도 합니다.
getInstance()가 호출될 때 SingletonHolder가 생성돼서 리소스 낭비도 없고, 멀티 쓰레드에도 안전하며 double checked locking처럼 복잡한 코드 없이 지연 초기화 방식(lazy initialization)으로 동작하기 때문입니다.
저는 static을 썼는데도 클래스가 로드될 때 초기화 되지 않고 lazy initialization을 가지고 있다 하여 처음엔 이해가 잘 되지 않았는데, jvm의 클래스 로딩 매커니즘에 따르면 외부 클래스가 로딩되어도 내부 정적 클래스는 자동으로 함께 로드되지 않는다고 합니다.
static 내부 클래스는 외부 클래스와 독립적으로 로드되기 때문에 내부 클래스에 접근하거나 사용할 때만 jvm에 의해 로드됩니다.
따라서 지연 초기화 방식으로 볼 수 있는 것입니다.
하지만 이 방법에도 단점은 존재합니다.
- 처음 접하는 사람에게 동작방식 등이 이해하기 어려울 수 있습니다.
- 리플렉션에 취약합니다.
- 직렬화 및 역직렬화 과정을 거치면 싱글톤이 깨질 수 있습니다.
결론
싱글톤 패턴은 다양한 방법으로 구현될 수 있으며, 각 방법마다의 장단점이 있습니다. 프로젝트의 요구사항, 사용 환경에 따라 적절한 싱글톤 구현 방식을 선택하는 것이 중요합니다.
여러가지 방법을 이해하고 상황에 따라 적절하게 사용하면, 싱글톤 패턴을 더 효과적으로 활용할 수 있을 것입니다.
이상으로 포스팅을 마치며, 다음 포스팅에서는 싱글톤을 깨트리는 방법 및 대응 방법에 대해서도 알아보겠습니다.
감사합니다.
'Programming' 카테고리의 다른 글
[Java]싱글톤 패턴 깨트리는 방법 및 대응 방법 (0) | 2023.08.07 |
---|---|
[Rust] Rust에 대하여 (0) | 2023.08.03 |
React란? (0) | 2023.07.24 |
안드로이드 앱개발(2) - 로또 번호 생성 앱 (0) | 2023.07.20 |
[Vue] Vue Router (0) | 2023.07.09 |
댓글