디자인 패턴중에 하나로써,
객체의 인스턴스가 오로지 한개만 생성되도록 설계하는 것을 말해요
로그 기록, 캐싱, 사용자 설정등에 사용 해요
해당 인스턴스가 프로그램 전역적으로 사용되고, 유일할 때 적용할 수 있는 방법이에요
예시를 들어 볼게요!
어떤 애플리케이션의 배경 설정한다고 생각해 볼까요?
Settings를 배경색을 담당하는 클래스라고 생각해 볼게요.
Setfings가 애플리케이션 내부에서 유일하지 않다면?
애러가 발생할 꺼에요!
(배경색을 지정하는 Settings가 여러개라면 여러가지 배경 색을 지정한 결과가 섞여서 오류가 발생하겠죠!?)
public class App {
public static void main(String[] args) {
Settings settings1 = new Settings);
Settings settings2 = new Settings();
System.out.println(settings1);
System.out.println(settings2);
System.out.printlnsettings1 == settings2);
}
}
<출력 내용>
game.Settings@2c8d66b2
game.Settings@5a39699c
// 서로 다른 인스턴스 2개가 생성되었습니다.
false
5가지 싱글톤 구현 방법에 대해 이야기해 보려고 해요!
public class Settings {
private static Settings instance;
private Settings() { // private으로 외부 생성자 막기
}
public static Settings getInstance() {
if (instance == null) { // 문제가 되는 부분
instance = new Settings ();
}
return instance;
}
}
public class Settings {
private static Settings instance;
private Settings() {
}
// Synchronized 부분에 진입하면 락을 걸고 instance가 있는지 확인해요.
// 계속 확인하면서 락이 걸리니까 비효율 적이에요
public synchronized static Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
}
public class Settings {
private static volatile Settings instance;
private Settings() {
}
public static Settings getInstance) {
if (instance == null) {
synchronized (Settings.class) {
if (instance == null) {
instance = new Settings);
}
}
}
return instance;
}
}
volatile이라는 키워드를 통해서
대입과 읽는 것 모두 메인 메모리에서 하는것으로 만들어서
시간차를 극복할 수 있습니다.
public class Settings {
private Settings() {
}
private static class SettingsHolder {
private static final Settings SETTINGS = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.SETTINGS;
}
}
싱글톤을 구현할 때 권장되는 방법이에요!
(Initialization on demand holder를 이용하는 방법)
Holder 역할을 하는 private 역할을 하는 private static 클래스를 이용하는 것입니다.
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock (name )) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass (name) ;
if (c == null) {
// 이하 생략...
최초로 Classloader에 의해서 로드 될 때 내부로 synchronized가 실행 됩니다.
그래서 명시적으로 synchronized를 이용하지 않고 동일한 효과를 낼 수 있습니다.
해당 클래스는 static 이므로 method가 실행될 때, JVM의 static initializer에 의해 초기화 되고, 메모리로 올라가게 됩니다.
따라서 thread-safe와 lazy-loading을 모두 만족하는 싱글톤 패턴을 구현 가능합니다.
하지만 해당 코드의 문제점은
클라이언트가 임의로 싱글톤을 파괴할 수 있다는 것입니다.
리플렉션과 직렬화를 통해 파괴될 수 있습니다.
A. Instance
public enum Settings {
INSTANCE;
}
Enum은 애초에 생성자를 private으로 갖게 만들고 상수만 받는 클래스이기 때문에 싱글톤의 성질을 갖게 됩니다.
이렇게 구현하게 되면 리플렉션과 직렬화로 깨트릴 수 없게 됩니다.
하지만, 싱글톤에서 멀티톤으로 변경하려고 할 때 코드를 다시 짜야하고,
enum외의 클래스를 상속한다면 사용할 수 없다는 단점이 있습니다.
Holder
Enum
자바에는 정적 클래스가 따로 없어요!
편의상 정적 메서드만을 갖고 있는 클래스를 정적 메서드라고 부를게요
스테틱 메서드들은 클래스 초기화시에 메서드 영역에 등록되어 프로그램이 끝날 때 해제됩니다.
따라서 애플리케이션 내에서 싱글턴과 마찬가지로 전역적으로 사용할 수 있고, 인스턴스를 따로 생성하지 않기 때문에 유일성을 보장받을 수 있습니다.
차이점으로는 인스턴스를 따로 생성할 수 없기 때문에 클래스 메서드를 이용합니다.
정적 클래스
1. 객체 생성을 하지 않아요.(효율성)
정적 클래스는 객체처럼 사용할 수는 없지만 컴파일시 정적 바인딩이 되기 때문에 보통 싱글톤보다 효율이 좋습니다.
그래서 유틸 클래스처럼 객체의 성질이 필요 없을 때 사용하는 것이 권장됩니다.
어떤 방법을 사용하던 정답은 없지만,
두개의 스타일에 대한 특성을 이해하고, 타당한 근거를 생각해보고 적용해서 사용해야 합니다.
더블톤은 안되나요? 싱글은 외로와요