싱글톤 패턴은 인스턴스를 불필요하게 생성하지 않고 오직 한 개의 인스턴스만 생성하여 사용되는 디자인 패턴이다. 즉, 생성자의 호출이 반복적으로 이루어져도 실제로 생성되는 객체는 최조 생성된 객체를 반환 해주는것 이다.
싱글톤 패턴을 사용하는 이유
인스턴스를 오직 한 개만 생성하여 사용한다면 어떤 장점이 있을까?
메모리 낭비 방지
이미 생성된 인스턴스를 활용함으로써 속도 측면에서도 장점이 있다고 볼 수 있다.
다른 클래스 간에 데이터 공유가 쉽다 → 싱글톤으로 생성 객체는 전역성을 띄기 때문에 다른 객체와 공유가 용이 하다 하지만 만약 여려 클래스의 인스턴스에서 싱글톤 인스턴스의 데이터에 동시에 접근하게 된다면 동시성 문제가 생길 수 있다.
인스턴스가 한 개만 존재하는 것을 보증하고 싶은 경우 싱글톤 패턴을 사용한다
싱글톤 패턴의 문제
싱글톤 패턴을 구현하는 코드가 많이 필요하다.
정적 메소드에서 객체 생성을 체크하고 생성자를 호출하는 경우 멀티스레딩 환경에서 발생할 수 있는 동시성 문제 해결을 위해 syncronized 키워드를 사용해야 한다
테스트하기 어렵다는것이다.
싱글톤 인스턴스는 자원을 공유하기 때문에 테스트시 매번 인스턴스의 상태를 초기화 시켜줘어야 한다.
싱글톤으로 만든 인스턴스의 역할이 복잡한 경우 해당 싱글톤 객체를 사용하는 다른 객체간의 결합도가 높아져 객체 지향 설계 원칙에 어긋나게 된다.(개방-폐쇄 원칙)
이외에도 자식클래스를 만들 수 없다는 점과, 내부 상태를 변경하기 어렵다는 점 등 여러가지 문제점들이 존재한다.
다양한 싱글톤 패턴 구현 방식
Lazy initialization(늦은 초기화) 싱글톤 패턴
public calss Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
static한 자기자신의 클래스를 필드로 만들고, 인스턴스가 필요하여 요청할 때 생성되는 형태로 작성되었다.
private 생성자와 static 메소드를 사용한 가장 보편적인 방식이다. 하지만 위의 코드는 멀티쓰레드 환경에서 취약하다는 문제점이 있다.
예를 들어, 쓰레드A와 쓰레드ㅠ가 있다고 하자. 쓰레드A가 getInstance() 메소드의 if문을 지날 때 instance가 null이라면 쓰레드A은 인스턴스를 생성하려고 할 것이다. 이 때 쓰레드B로 제어권이 넘어간다면 쓰레드B 역시 if문이 수행되면 쓰레드B 또한 인스턴스 생성할 것이다. 이렇게 되면 인스턴스가 두 번 생성되는 문제가 발생한다.
synchronized를 추가해 멀티 쓰레드 환경에서 안전하게 구현
public class Singleton{
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) {
instace = new Singleton();
}
return instance;
}
}
Lazy initialization의 쓰레드 동기화 문제의 가장 쉬운 해결방법은 synchronized(동기화) 키워드이다.
쓰레드가 synchronized 되어있는 곳에서 작업하고 있다면 다른 쓰레드가 접근하지 못하도록 lock 걸어줌
getInstance() 메소드를 synchronized로 처리하면 멀티 쓰레드의 동시 접근에 대한 문제는 해결하게 된다. 하지만, 이 방법은 매번 인스턴스를 리턴 받을 때마다 쓰레드를 동기화하기 때문에 성능 저하가 생긴다는 단점이 있다.
실제로 인스턴스가 2개 이상 생성될 확률은 매우 적다. 이 때문에 synchronized를 추가했지만, 인스턴스 초기화가 완료된 시점 이후라면 synchronized는 불필요하게 사용되고, 별다른 역할을 하지 못한다.