싱글톤 패턴
구현방법
1.
public class Settings {
private static Settings instance;
public Settings() {
}
public static Settings getInstance(){
if(instance == null){
instance = new Settings();
}
return instance;
}
}
- 두개의 요청중 첫번째 요청에서 new Setting을 통해 인스턴스를 만들기전
또 다른 요청이 들어오면 첫번째 요청에서 생성되기 직전이라 하나를 더 만들게된다.(쓰레드세입하지 않음)
2.
public class Settings {
...
public static synchronized Settings getInstance(){
if(instance == null){
instance = new Settings();
}
return instance;
}
}
- 해결방법 : synchronized를 사용해주면된다.
- 또다른 문제: 하나의 요청을 만들때 락이 걸리면서 다른 요청은 먼저 들어온 요청이 끝날때까지 기다리게되고, 성능 이슈가 발생
3. 이른 초기화 사용
public class Settings {
private static final Settings INSTANCE = new Settings();
public Settings() {
}
public static Settings getInstance() {
return INSTANCE;
}
}
- 쓰레드 safe함, 몇번의 요청이 들어와도 이미 만들어져있기 때문에
- 단점: 미리 만든다는 자체, 인스턴스를 만드는데 굉장히 오래걸리고, 많은 메모리를 잡아먹는다면 효율적이지 않음
4. double checked locking 사용하기
public class Settings {
private static volatile Settings INSTANCE;
public Settings() {
}
public static Settings getInstance() {
if (INSTANCE == null) {
synchronized (Settings.class) {
if (INSTANCE == null) {
INSTANCE = new Settings();
}
}
}
return INSTANCE;
}
}
- 두개의 요청중 첫번째 요청 처리중 또다른 요청이 들어와도, synchronized로 인해 중복으로 생성될 가능성이 없음,
- volatile를 써줘야하는데 1.4버전 이후에서만 동작하고, 이것을 사용하려면 1.4버전에서 멀티쓰레드가 메모리를 어떻게 공유하는지에 대해 깊게 알아야함
5. static inner 클래스 사용하기
public class Settings {
private Settings() {
}
private static class SettingsHolder {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.INSTANCE;
}
}
- 클래스 로딩 : 클래스에 static 필드가 있고 static 필드에 대한 접근이 이루어지면 클래스가 로드된다.
- BillPughSingleton 클래스에도 doNothing() 이라는 아무것도 하지 않는 static 메소드를 선언하고, 이에대한 호출을 하면 BillPughSingleton 클래스가 로드되는 것을 확인할 수 있다. (jvm 옵션에 -verbose:class 주고 실행) 이때 doNothing() 만 호출해서는 SingletonHelper 클래스는 로드되지 않는 것도 확인할 수 있다. 만약 getInstance() 호출로 SingletonHelper 클래스에 대한 사용이 이루어지는 코드를 삽입하면 SingletonHelper 클래스가 로드되는 것을 확인할 수 있다.
- 참고 : https://yaboong.github.io/design-pattern/2018/09/28/thread-safe-singleton-patterns/
6. 싱글톤 패턴 구현 방법을 깨뜨리는 방법
- 리플렉션 사용
public class App {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Settings settings = Settings.getInstance();
Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
constructor.setAccessible(true);
Settings settings1 = constructor.newInstance();
System.out.println(settings == settings1);
}
}
직렬화 & 역직렬화
public class Settings implements Serializable {
///
}
public class App {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
Settings settings = Settings.getInstance();
Settings settings1 = null;
//직렬화
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("settings.obj"));
out.writeObject(settings);
//역직렬화
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
settings1 = (Settings) in.readObject();
}
System.out.println(settings == settings1);
}
}
직렬화 역직렬화 막는 방법 : 역직렬화할때 readResolve()라는 메소드를 사용하게 되는데,
아래와 같이 작성하여 getInstance를 전달해주면됨
역직렬화 할때 생성자를 만들어 값을 넣어주게 되는데, 그것을 활용하여 코드를 작성
public class Settings implements Serializable {
private Settings() {
}
private static class SettingsHolder {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.INSTANCE;
}
protected Object readResolve() {
return getInstance();
}
}
7. 안전하고 단순하게 구현하는 방법
enum (enum은 Serialize를 구현하고 있으며, Reflaction을 자체적으로 막아 놓고 있다.)
public enum Settings {
INSTANCE;
Settings() {
}
private Integer number;
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
}
단점: 미리 생성한다는점
8. 자바와 스프링에서 찾아보는 패턴
- 스프링에서 빈 스코프 중에 하나로 싱글톤 스코프
- java.lang.Runtime
- 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리등) 구현체의 일부로도 사용됨