Search

Chapter 6. 싱글톤 패턴

생성일
2025/06/01 07:35
태그

싱글톤 패턴

싱글톤이란 ‘단 하나의 원소만을 가진 집합’이라는 수학 이론에서 유래된 것으로, 싱글톤 패턴은 인스턴스가 오직 하나만 생성되는 것을 보장하고 어디에서든 해당 인스턴스에 접근할 수 있도록 하는 디자인 패턴이다.
싱글톤 패턴은 인스턴스를 보관하는 정적 변수와 private 생성자, 인스턴스에 접근할 수 있는 getter 밖에 없다.
싱글톤 패턴을 순차 다이어그램으로 표현하면 위와 같은데, getInstance를 호출 했을 때 인스턴스가 이미 생성되어 있다면 반환하고 없다면 생성자를 호출해 생성 후 저장 및 반환한다.

싱글톤 패턴 예시

단 하나의 Printer

만약 하나의 프린터를 통해 여러 직원이 공유해서 사용해야하는 상황과 같이 단 하나의 프린터로만 작업을 해야하는 상황을 가정해보자.
public class Printer { public Printer() { } public void print(final Resource r) { //... } }
Java
복사
만약 이와 같이 Printer 클래스를 작성한다면, 클라이언트에서는 new Printer()가 반드시 한 번만 호출되도록 매우 주의를 기울여야 할 것이다.
public class Printer { private Printer() { } //... }
Java
복사
이와 같이 생성자 접근을 막아버린다면 외부의 new Printer() 호출을 막아낼 수 있다. 하지만 Printer의 인스턴스에 접근할 필요는 있기 때문에,
public class Printer { private static Printer INSTANCE; private Printer() { } public static Printer getPrinter() { if (INSTANCE == null) { INSTANCE = new Printer(); } return INSTANCE; } public void print(final Resource r) { } }
Java
복사
이처럼 내부 정적 변수로 Printer의 인스턴스를 두고 호출 시점에 단 한번만 생성하도록 구현할 수 있다. 이와 같이 구현해두면, getPrinter()를 몇 번 호출하든지 상관없이 Printer 클래스의 인스턴스는 단 하나만 생성되는 것이 보장된다.

문제점과 해결책

위 구현 방식에는 문제가 있다.
public static Printer getPrinter() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } INSTANCE = new Printer(); } return INSTANCE; }
Java
복사
public class Client { public static void main(String[] args) { for (int i = 0; i < 50; i++) { final Thread thread = new Thread(() -> { final Printer printer = Printer.getPrinter(); System.out.println("printer = " + printer); }); thread.start(); } } }
Java
복사
이처럼 Printer 인스턴스가 생성되지 않은 시점에, 여러 스레드에 동시에 getInstance를 호출하는 경우 Printer 인스턴스가 여러 개 생길 수 있다는 점이다.(위에서는 Thread.sleep을 통해 상황을 재현했다.)
이러한 문제는 두 가지의 해결 방법이 있다.
public class Printer { private static Printer INSTANCE = new Printer(); private Printer() { } public static Printer getPrinter() { return INSTANCE; } public void print(final Resource r) { } }
Java
복사
첫 번째는 정적 변수에 인스턴스를 생성해 초기화하는 방법이다. 정적 변수는 클래스가 메모리에 로딩될 때 만들어지고 초기화가 단 한번만 실행된다. 이를 통해 INSTANCE == null 조건에 대한 검증이 불필요해져 스레드 안전한 상태가 된다.
public synchronized static Printer getPrinter() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } INSTANCE = new Printer(); } return INSTANCE; }
Java
복사
두 번째는 인스턴스를 만드는 메서드를 sychronized 키워드로 임계 영역으로 지정해 동기화하는 방법이다.

정적 클래스

사실 위의 두 번째 해결 방법은 싱글톤 패턴이 아니더라도 정적 클래스로 구현해도 동일한 효과를 얻을 수 있다.
public class StaticPrinter { public synchronized static void print(Resource r) { // print } }
Java
복사
싱글톤 패턴과의 차이점은 객체를 전혀 인스턴스화 하지 않고 메서드만 호출한다는 점이다.
하지만 정적 클래스를 사용할 수 없는 경우가 있는데, 가장 대표적으로 인터페이스를 구현하는 경우이다.
public interface Printer { public static void print(Resource r); // 허용되지 않음 } public class RealPrinter315 implements Printer { // ... }
Java
복사
여러 프린터 중 하나를 선택해 프린트하거나, 테스트 코드를 작성하기 위해 테스트 더블을 만드는 경우 위 방식을 사용할 수 없다. 그러니 이런 경우 싱글톤 패턴을 사용해야한다.