인스턴스를 오직 한 개만 제공하는 클래스
- 시스템 런타임, 환경세팅에 대한 정보 등 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다.
인스턴스를 오직 한 개만 만들어 제공하는 클래스에 필요하다.
new 를 이용해서 인스턴스를 생성하면 얼마든지 생성할 수 있고 같은 클래스라 하더라도 같은 인스턴스가 아니다.
java에서 new를 이용해서 새로운 인스턴스를 생성하지 못하게 하려면 private을 이용해서 클래스내부 생성자를 만든다.
객체가 null일때 내부에서 new로 인스턴스를 생성
public class Settings {
private static Settings settings;
private Settings() {};
public static getInstance() {
if(settings == null) {
settings = new Settings();
}
return settings;
}
}
위와 같이 사용할 수 있지만 위의 방법은 멀티쓰레드에 안전하지 않다.
synchronized 키워드 사용
쓰레드세이프하기위해서는 synchronized 키워드를 사용할 수 있다.
public static synchronized Settings getInstance() {
}
이 방법의 문제는 synchronized를 사용하면 비용이 많이 든다.
getInstance를 호출할 때마다 비용이 발생한다.
이른 초기화
public class Settings {
private static final Settings settings = new Settings();
private Settings() {}
public static Settings getInstance() {
return settings;
}
}
미리 초기화를 하면 synchronized를 사용할 필요가 없다.
더블체크하기
public class Settings {
private static volatile Settings settings;
private Settings() {
}
public static Settings getInstance() {
if (settings == null) {
synchronized (Settings.class) {
if (null == settings) {
settings = new Settings();
}
}
}
return settings;
}
}
null체크를 두 번 한다.
멀티쓰레드가 빈번하게 일어나는 경우 훨씬 유리하다.
volatile은 자바 1.5이상부터 동작한다.
static inner class만들기
public class Settings {
private Settings() {
}
private static class SettingsHolder {
private static final Settings settings = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.settings;
}
}
static inner클래스로 만든 후 호출하면 좀 더 간편한 로직으로 구현이 가능하다.
BUT!
리플렉션을 이용하면 싱글톤이 깨진다.
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Settings settings = Settings.getInstance();
Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
constructor.setAccessible(true);
Settings settings2 = constructor.newInstance();
System.out.println(settings == settings2);
}
직렬화 역직렬화 사용시 싱글톤이 깨진다.
public class Settings implements Serializable {
private Settings() {
}
private static class SettingsHolder {
private static final Settings settings = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.settings;
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Settings settings = Settings.getInstance();
Settings settings2 = null;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
out.writeObject(settings);
}
try (ObjectInput input = new ObjectInputStream(new FileInputStream("settings.obj"))) {
settings2 = (Settings) input.readObject();
}
System.out.println(settings == settings2);
}
직렬화&역직렬화는 readResolve()메소드를 Settings안에 구현하면 해결할 수 있다.
protected Object readResolve() {
return getInstance();
}
enum을 사용하는 방법
public enum Settings {
INSTANCE;
private Settings() {
}
}
리플렉션을 막아놨다.
그래서 유일한 인스턴스가 보장된다.
단점은 미리 만들어진다는 것이다. 상속을 사용할 수 없다.
직렬화 역직렬화에 안정적이다.