싱글톤(SingleTon) 패턴

길셔·2022년 10월 10일
0

java

목록 보기
2/2

싱글톤이란,

  • 실글톤 패턴 알아야 할 것

    • 자바에서 enum을 사용하지 않고 싱글톤 패턴을 구현하는 방법은?
    • private 생성자와 static 메소드를 사용하는 방법과 단점은?
    • enum을 사용해 싱글톤 패턴을 구현하는 방법의 장점과 단점은?
    • static inner 클래스를 사용해 싱글톤 해턴을 구현하라.
  • 실글톤 패턴 실무에서 사용시,

    • 스프링에서 빈의 스코프 중에 하나로 싱글톤 스코프
    • 자바 java.lang.Runtime
    • 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.

싱글톤 고도화

방법 1. Lazy initialization ( 늦은 초기화 방식 )

public class Sesstings {

	private static Settings instance; 
	
	private Settings() {};
	
	public static Settings getInstance() {
		if(instance == null){
			instance  =  new Settings();
		}
		return instance;
	}
	
}
  • getInstance 메소드를 통해서만 접근이 가능하다.
  • 그래서 Instance가 null인 경우만 객체가 생성돼어 반환되고 이후는 만들어진 객체가 반환 된다.
  • 단점은 멀티쓰레드 환경에서 쓰레드 1개가 들어와서 null인 경우 객체를 생성하고 있는데, 다른 쓰레드도 들어와서 null인걸 확인하고 객체를 생성하면 중복과 오류가 날 수 있다.

방법 2. sychronized

public class Sesstings {

	private static Settings instance; 
	
	private Settings() {};
	
	public static sychronized Settings getInstance() {
		if(instance == null){
			instance  =  new Settings();
		}
	return instance;
	}

}
  • sychronized를 사용해서 쓰레드를 한 번에 1개씩만 처리하게 만드는 방법이다.
  • 이렇게 하면 중복이나 오류가 날 일은 없지만, 처리 속도가 느려진다는 단점이 있다.

방법 3. eager initialization (이른 초기화 방식)

public class Sesstings {

	private static final Settings INSTANCE= new Setiings(); 

	private Settings() {};

	public static Settings getInstance() {
		return INSTANCE;
	}
}
  • 이 방법은 INSTANCE가 클래스가 로딩되는 시점에 초기화 되고, INSTACNE는 return 되기만 하니까 멀티쓰레드 환경에서 safe 하다.
  • 다만 단점이 미리 만들어야 한다는 것이 단점이다.
  • 만약 INSTANCE를 만드는 과정이 굉장히 길고 복잡한데 사용하지를 않는다면 로딩 시 많은 리소스를 요구한 것에 의미가 없어진다.

방법 4. DOUBLE CHECKED LOCKING

public class Sesstings {

		private static volatile Settings instance; 
	
		private Settings() {};
	
		public static Settings getInstance() {
				if(instance == null) {
					sychronized (Settings.class) {
						if(instance == null){
							instance  =  new Settings();
						}
					}
				}
		return instance;
		}
}
  • volatile를 써줘야 java1.5이상부터 동작하는 DOUBLE CHECKED LOCKING이 완성된다.
  • 이 방법은 getInstance() 메소드를 호출할 때 있으면 스킵하고 없을 때마다 sychronized가 동작하기 때문에 앞의 방법들보다 더 효율적이다.
  • 멀티 쓰레드가 if문 안에 동시에 여러 개가 들어와서 많은 트래킹을 처리하더라도 그때만 sychronized를 사용하기에 성능적으로 더 유용하고, getInstance() 메소드가 호출시에만 만들어지기 때문에 더 효율적이다.

방법 5. STATIC INNER 클래스 사용한 방법

public class Sesstings {

		private Settings() {};
	
		private static class SettingsMolder{
			private static final Settings INSTANCE = new Settings();
		}
		
		public static Settings getInstance() {	
			return SettingsMolder.INSTANCE;
		}
}
  • 이와 같은 방법이 멀티 쓰레드 환경에서도 안전하고, getInstance() 메소드가 호출될 때 로딩이 돼서 LAZY HOLDER도 되고, 이전에 사용했던 DOUBLE CHECKED LOCKING 보다 코드도 간결하다.

싱글톤을 깨트리는 방법

방법 1. 리플렉션

public class App{
	
	public static void main(String[] args) throws NosuchMethodException, InvocationTargerException, InstanceException{
		Settings settings = Settings.getInstance();
		
		Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
		constructor.setAccessible(true);
		Settings settings1 = constructor.newInstance();

		System.out.println(settings == settings1);
	}
}

방법 2. 직렬화 & 역직렬화

//먼저 settings에 implements Serializable 을 선언해주고 시작해야한다.

public class App{
	
	public static void main(String[] args) throws NosuchMethodException, InvocationTargerException, InstanceException{
		Settings settings = Settings.getInstance();

		try(ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
			out.writeObject(settings);
		}

		try(ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
			settings1 = (Settings)in.readObject();
		}
		
		System.out.println(settings == settings1);
	}
}

대응 방안

protected Object readResolve() {
		return getInstance();
}
  • Settings에서 readResolve()를 통해서 getInstance() 메소드를 호출하게 하는 방법이다.

  • 이렇게 작성하면 역직렬화 상황 시 getInstance()가 호출되게 돼서 해결된다.

  • 근데 리플렉션은 대응이 안되기 때문에 enum을 쓰면 된다.

public enum Settings {

	INSTANCE;

	private Integer number;

	public Integer getNumber(){
		return number;
	}

	public void setNumber(Integer number) {
		this.number = number;
	}

}
  • 그러면 맨위의 직렬화에서 Settings.getInstance();Settings.INSTANCE 로 바꾸면 된다.
  • 이 방법은 리플렉션으로 절대 뚫을 수 없고, 리플렉션으로부터 안전한 코드가 된다.
  • enum은 기본적으로 Serializable를 선언해준다.
profile
까먹지말자

0개의 댓글