싱글톤 패턴

JUNHO YEOM·2023년 2월 7일
0

개념

목록 보기
6/6

싱글톤 패턴

디자인 패턴중에 하나로써,
객체의 인스턴스가 오로지 한개만 생성되도록 설계하는 것을 말해요
로그 기록, 캐싱, 사용자 설정등에 사용 해요
해당 인스턴스가 프로그램 전역적으로 사용되고, 유일할 때 적용할 수 있는 방법이에요

싱글턴 패턴이 필요한 상황

예시를 들어 볼게요!
어떤 애플리케이션의 배경 설정한다고 생각해 볼까요?
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가지 싱글톤 구현 방법에 대해 이야기해 보려고 해요!

  1. 순수한 구현
  2. 동기화
  3. Double Checked Locking
  4. Bill Pugh Solution
  5. Enum


1. 순수한 구현

  1. 인스턴스를 private static 변수로 선언해요!
  2. getInstance()에서 인스턴스 생성
  3. Settings의 생성을 private으로 설정해서 외부 생성자를 막아요

코드

public class Settings {
	private static Settings instance;

	private Settings() { // private으로 외부 생성자 막기 
	}
	public static Settings getInstance() {
		if (instance == null) { // 문제가 되는 부분
			instance = new Settings ();
        }
		return instance;
	}
}

문제점

  • 멀티쓰레드 환경에서 싱글톤이 보장되지 않아요!
  • 멀티쓰레드 상태에서 쓰레드가 동시에 조건문을 통과하게 된다면, 새로운 instance가 2개 만들어 질꺼에요!


2. 동기화

  1. 인스턴스를 private static 변수로 선언해요
  2. 멀티쓰레드 상태에서 인스턴스가 동시에 생성될 가능성을 제어하기 위해 동기화를 사용해요! synchronized getinstance()
  3. 외부 생성자를 막아요!
public class Settings {
	private static Settings instance;
	
    private Settings() {
	}
	
    // Synchronized 부분에 진입하면 락을 걸고 instance가 있는지 확인해요.
    // 계속 확인하면서 락이 걸리니까 비효율 적이에요
	public synchronized static Settings getInstance() {
		if (instance == null) {
			instance = new Settings();
		}
	return instance;
	}
}

문제점

  • 리소스의 낭비
    멀티쓰레드 환경에서 인스턴스를 하나만 만들기 위해 synchronized를 이용했는데,
    인스턴스가 존재한다면 더이상 필요가 없게 됩니다. 괜히 메서드를 실행할 때 마다 락이 걸려 리소스가 낭비됩니다.

3. Double Checked Locking(DCL)

  1. synchronized 시점 지연
  2. private static volatile 인스턴스
  3. 외부 생성자를 막는다.
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;
	}
}
  1. 현재 get Instance method를 호출할 때마다,
    instance가 있을때는 synchronized블록이 skip됩니다.
    그럼 즉시 인스턴스만 반환하게 되어 resource낭비를 없앨 수 있습니다.
    이때 클래스 변수에 정의해놨던 volatile이라는 것을 이용해야 하는데
    스레드를 이용하게 되면 각각의 쓰레드는 성능을 위해 캐시 메모리를 사용하게 됩니다.
    첫번째 스레드가 캐시 메모리에서 메인 메모리로 값을 대입해주면
    다음 스레드는 메인 메모리에 담긴 값을 캐시 메모리에 담아 들여오는 방식으로 동작합니다.
    문제는 첫번째 스레드가 메인 메모리의 값을 대입하기 전에 다음스레드가 메인 메모리에서 값을 읽어드리려고 할 때 발생합니다.

volatile이라는 키워드를 통해서
대입과 읽는 것 모두 메인 메모리에서 하는것으로 만들어서
시간차를 극복할 수 있습니다.

문제점

  • JDK 1.5이상에서만 구현 가능
  • JVM종류에 따라서 thread-safe하지 않은경우가 발생 가능
  • 사용이 어려워요(자바가 어떻게 메모리를 관리하는지 이해해야 합니다)

4. Bill Pugh Solution (Initialization on demand holder idiom)

  1. static inner class 인스턴스
  2. 생성자를 private
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을 모두 만족하는 싱글톤 패턴을 구현 가능합니다.

하지만 해당 코드의 문제점은
클라이언트가 임의로 싱글톤을 파괴할 수 있다는 것입니다.
리플렉션과 직렬화를 통해 파괴될 수 있습니다.


싱글톤 구현 5.

Enum

A. Instance

public enum Settings {
	INSTANCE;
}

Enum은 애초에 생성자를 private으로 갖게 만들고 상수만 받는 클래스이기 때문에 싱글톤의 성질을 갖게 됩니다.
이렇게 구현하게 되면 리플렉션과 직렬화로 깨트릴 수 없게 됩니다.

하지만, 싱글톤에서 멀티톤으로 변경하려고 할 때 코드를 다시 짜야하고,
enum외의 클래스를 상속한다면 사용할 수 없다는 단점이 있습니다.

추천 방법

Holder
Enum


정적 클래스와의 차이점

정적 클래스

자바에는 정적 클래스가 따로 없어요!
편의상 정적 메서드만을 갖고 있는 클래스를 정적 메서드라고 부를게요
스테틱 메서드들은 클래스 초기화시에 메서드 영역에 등록되어 프로그램이 끝날 때 해제됩니다.
따라서 애플리케이션 내에서 싱글턴과 마찬가지로 전역적으로 사용할 수 있고, 인스턴스를 따로 생성하지 않기 때문에 유일성을 보장받을 수 있습니다.

차이점으로는 인스턴스를 따로 생성할 수 없기 때문에 클래스 메서드를 이용합니다.


언제 쓰는게 좋을까?

싱글톤

  1. 상속받아서 사용가능
  2. 메서드의 파라미터로 사용 가능

권장 환경

  1. 완벽한 객체지향을 필요로 할 때
  2. 레이지 로딩이 필요할 때
    애플리케이션 내에서 객체처럼 사용하고 싶을때
    인스턴스 생성시 리소스가 많이 드는경우 권장되어요

정적 클래스

정적 클래스
1. 객체 생성을 하지 않아요.(효율성)

권장 환경

  1. 유틸 에서드를 보관하는 용도로 사용할 때
  2. 다형성이나 상속이 필요없는 클래스

정적 클래스는 객체처럼 사용할 수는 없지만 컴파일시 정적 바인딩이 되기 때문에 보통 싱글톤보다 효율이 좋습니다.
그래서 유틸 클래스처럼 객체의 성질이 필요 없을 때 사용하는 것이 권장됩니다.


결론

어떤 방법을 사용하던 정답은 없지만,
두개의 스타일에 대한 특성을 이해하고, 타당한 근거를 생각해보고 적용해서 사용해야 합니다.

2개의 댓글

comment-user-thumbnail
2023년 2월 7일

더블톤은 안되나요? 싱글은 외로와요

답글 달기
comment-user-thumbnail
2023년 2월 9일

벙글이도 오나요?

답글 달기