프로그램이 시작될 때 어떤 클래스가 최초 한번만 메모리를 할당하고(static) 그 메모리에 인스턴스를 만들어 생성된 인스턴스를 어디에서든지 참조할 수 있도록 하는 패턴이다. 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이다.
단 하나만 존재하는 인스턴스인 경우 생성자는 private으로 만들고, static으로 유일한 객체를 생성하고 외부에서 유일한 객체를 참조할 수 있는 public static get() 메서드 구현한다. 객체 인스턴스가 필요하면 getInstance() 메서드를 통해서만 조회할 수 있고, 이 메서드를 호출하면 항상 같은 인스턴스를 반환한다.
// 방법1: 객체를 미리 생성해두는 방법 (가장 단순하고 안정적인 방법)
public class SingletonService {
// static 영역에 객체를 딱 1개만 생성해둔다.
private static final SingletonService instance = new SingletonService();
// 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
private SingletonService() {}
// public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.
public static SingletonService getInstance() {
return instance;
}
}
// 방법2: getInstance()를 호출하는 시점에 객체가 생성이 안되어 있으면 객체를 생성하고, 이미 생성되어 있는 경우는 생성된 객체를 반환하는 방법
public class SingletonService {
private static SingletonService instance = new SingletonService();
private SingletonService() {}
public static SingletonService getInstance() {
if(instance == null) {
instance = new SingletonService();
}
return instance;
}
}
최초 한 번만 메모리를 할당하여 한 번의 new로 인스턴스를 사용하기 때문에 동일한 인스턴스를 자주 생성해주어야 하는 경우에 메모리 낭비를 방지한다. 싱글톤으로 생성된 객체는 한 번 생성으로 전역성을 띄기에 다른 객체와 공유가 용이하다.
상태를 가진 객체를 Singleton 으로 만들면 안된다. 단 하나의 인스턴스가 존재하고 전역에서 접근할 수 있기 때문에 다른 쓰레드에서 객체의 상태를 계속 변경할 수 있기 때문이다.
싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우 다른 클래스의 인스턴스들 간에 결합도가 높아져 OCP(개방-폐쇄 원칙)를 위배하게 된다.
값을 변경할 수 있는 필드 대신에 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
멀티쓰레드 환경이라면 static 앞에 synchronized 키워드를 붙여 동기화 작업을 추가로 해야한다. 단, 성능은 저하된다.