본문 바로가기

Computer Science/Design Pattern

[디자인 패턴] Singleton 패턴

정의

- 싱글톤 패턴은, 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다. 보통 데이터베이스 모듈에 많이 사용한다.

- 하나의 인스턴스를 만들어 놓고, 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에, 인스턴스를 생성할 때 드는 비용이 줄어드는 장점이 있다.

자바에서의 싱글톤 패턴

자바로는 다음과 같이 중첩 클래스를 이용해서 만드는 것이 가장 대중적인 방법이다.

class Singleton {
	private static class singleInstanceHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	public static Singleton getInstance() {
		return singleInstanceHolder.INSTANCE;
	}
}

public class HelloWorld {
	public static void main(String[] args) {
		Singleton a = Singleton.getIntance();
		Singleton b = Singleton.getInstance();
		System.out.println(a.hashCode());
		System.out.println(b.hashCode());
		if(a == b) {
			System.out.println(true);
		}
	}
}

/*
705927765
705927765
true
*/

 

자바로 싱글톤 패턴을 구현하는 6가지 방법

1. 단순한 메서드 호출

- 문제점: 원자성이 결여되어있다 => 자바에서는 멀티 스레드로 동작하므로, 문제가 발생할 수 있다. 즉, 여러 스레드에서 동시에 인스턴스를 생성해서 여러개의 인스턴스가 생길 수 있다는 문제가 있다.

- 해결방법: syncronized 키워드를 메서드 앞에 붙혀서 해당 문제점을 해결할 수 있다. 하지만 락이 걸려있으므로, 성능 저하 이슈가 있을 수 있다.

public class Singleton {
	private static Singleton instance;

	private Singleton() {

	}

	public static Singleton getInstance() {
		if (instance == null)
			instance = new Singleton();
		
		return instance;
	}
}

 

2. 정적 멤버

- 정적(static) 멤버나 블록은 런타임이 아니라, 최초에 JVM이 클래스 로딩 때 모든 클래스를 로드할 때 미리 인스턴스를 생성하는데, 이를 이용한 방법이다.

- 클래스 로딩과 동시에 싱글톤 인스턴스를 만들고, 그렇기 때문에 모듈들이 싱글톤 인스턴스를 요청할 때 그냥 만들어진 인스턴스를 반환하면 된다.

- 문제점: 불필요한 자원 낭비라는 이슈가 있다. 싱글톤 인스턴스가 필요없을 경우에도, 무조건 싱글톤 클래스를 호출해 인스턴스를 만들어야 하기 때문이다.

public class Singleton {
	private final static Singleton instance = new Singleton();

	private Singleton() {

	}

	public static Singleton getInstance() {
		return instance;
	}
}

  

3. 정적 블록

public class Singleton {
	private static Singleton instance = null;

	static {
		instance = new Singleton();
	}

	private Singleton() {

	}

	public static Singleton getInstance() {
		return instance;
	}
}

 

4. 정적 멤버와 LazyHolder (중첩 클래스)

- singleInstanceHolder라는 내부 클래스를 하나 더 만듬으로써, Singleton 클래스가 최초로 로딩되더라도 함께 초기화가 되지 않고, getInstace()가 호출될 때, singleInstanceHolder 클래스가 로딩되어 인스턴스를 생성하게 된다.

class Singleton {
	private static class singleInstanceHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	public static Singleton getInstance() {
		return singleInstanceHolder.INSTANCE;
	}
}

 

5. 이중 확인 잠금(DCL)

- 이중 확인 잠금은, 인스턴스 생성 여부를 싱글톤 패턴 잠금 전에 한번, 객체를 생성하기 전에 한번 2번 체크하면 인스턴스가 존재하지 않을때만 잠금을 걸 수 있기 때문에 앞서 생겼던 문제점들을 해결할 수 있다.

public class Singleton {
	private volatile Singleton instance;
	
	private Singleton() {

	}

	public Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null)
					instance = new Singleton();
			}
		}
	}
}

 

6. enum

- enum 클래스는 기본적으로 thread safe한 점 보장되기 때문에, 이를 통해 생성할 수 있다.

public enum SingletonEnum {
	INSTANCE;
	public void oortCloud() {

	}
}

위 6가지 방법에서 추천하는 방법은 제일 많이 쓰이는 4번(정적 멤버와 LazyHolder)와 6번이다.
6번은 이펙티브 자바를 쓴 조슈아 브로크가 추천한 방법이다.

 

싱글톤 패턴의 단점

  • 싱글톤 패턴은 TDD할 때 걸림돌이 된다.
    • TDD를 할 때, 단위 테스트는 테스트가 서로 독립적이어야 하며, 테스트를 어떤 순서로든 실행할 수 있어야한다.
    • 하지만, 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로, 각 테스트마다 독립적인 인스턴스를 만들기가 어렵다.
  • 의존성 주입
    • 싱글톤 패턴은 모듈 간의 결합을 강하게 만들 수 있다는 단점이 존재한다. 이를 의존성 주입을 통해 모듈간의 결합을 조금 더 느슨하게 만들어 해결할 수 있다.
    • 메인 모듈이 직접 다른 하위 모듈에 대한 의존성을 주기 보다는, 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 간접적으로 의존성을 주입하는 방식이다.
    • 의존성 주입을 할때는, 상위 모듈은 하위 모듈서 어떠한 것도 가져오지 않아야한다. 또한, 둘 다 추상화에 의존해야 하며, 이때 추상화는 세부사항에 의존하지 말아야한다. 라는 의존성 주입 원칙을 지켜주면서 만들어야 한다.

 

참고자료

면접을 위한 CS 전공지식 노트

https://www.youtube.com/watch?v=4Sk9dzXgKwo