자바 - 싱글톤 클래스

안녕하세요, 미래의 자바 마법사 여러분! 오늘 우리는 자바 프로그래밍에서 가장 흥미로운 개념 중 하나인 싱글톤 클래스에 대해 깊이 다루어보겠습니다. 프로그래밍 초보자라도 걱정하지 마세요; 저는 여러분을 단계별로 안내해드릴 것입니다. 수년간 수많은 학생들을 가르쳐온 경험을 바탕으로 말이죠. 그럼, 좋아하는 음료를 한 잔 챙기고 편안하게 앉아, 이 흥미로운 여정을 함께 떠나보겠습니다!

Java - Singleton Class

싱글톤 클래스는 무엇인가요?

Exclusive한 클럽의 매니저로 상상해보세요. 이 클럽은 매우 Exclusive해서 전 세계에 단 한 개의 인스턴스만 존재할 수 있습니다. 자바에서 싱글톤 클래스는 바로 이와 같은 개념입니다 - 자신의 인스턴스가 단 한 개만 생성되도록 허용하는 클래스입니다.

왜 싱글톤을 사용할까요?

"단 하나의 인스턴스로 제한해야 하는 이유가 뭐가 있을까요?"라고 궁금할 수 있습니다. 여러 가지 이유가 있습니다:

  1. 전역 접근점: 특정 인스턴스에 대한 단일 접근점을 제공하여 애플리케이션 내 전역 상태를 쉽게 유지할 수 있습니다.
  2. lazy initialization: 필요할 때만 인스턴스가 생성되므로 자원을 절약할 수 있습니다.
  3. 스레드 안전성: 제대로 구현되면 스레드 안전하며, 다중 스레드 환경에서도 단일 인스턴스를 보장할 수 있습니다.

이제 자바에서 싱글톤 클래스를 어떻게 생성할 수 있는지 살펴보겠습니다.

싱글톤 클래스 생성하기

싱글톤 클래스를 생성하는 방법은 여러 가지가 있지만, 가장 간단하고 일반적인 방법인 eager initialization으로 시작해보겠습니다.

public class EagerSingleton {
// 클래스의 사적인 정적 인스턴스
private static final EagerSingleton instance = new EagerSingleton();

// 인스턴스 생성을 방지하는 사적인 생성자
private EagerSingleton() {}

// 인스턴스를 반환하는 공개 정적 메서드
public static EagerSingleton getInstance() {
return instance;
}
}

이를 해부해보면:

  1. 사적인 정적 final 변수 instance를 선언합니다. 이는 영원히 존재할 인스턴스입니다.
  2. 생성자는 사적인 것이므로 다른 클래스에서 인스턴스를 생성할 수 없습니다.
  3. 공개 정적 메서드 getInstance()를 제공하여 단일 인스턴스를 반환합니다.

이 싱글톤을 사용하려면 다음과 같이 합니다:

EagerSingleton singleton = EagerSingleton.getInstance();

간단하죠? 하지만 인스턴스를 필요할 때만 생성하고 싶다면? 그때는 lazy initialization이 필요합니다.

Lazy Initialization

public class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {}

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

이 버전에서는 getInstance()가 처음 호출될 때 인스턴스가 생성됩니다. 그러나 이는 스레드 안전하지 않습니다. 다중 스레드 환경에서 여러 개의 인스턴스가 생성될 수 있습니다. 이를 해결해보겠습니다!

스레드 안전한 싱글톤

public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;

private ThreadSafeSingleton() {}

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

getInstance() 메서드에 synchronized 키워드를 추가하여 단일 스레드가 이 메서드를 동시에 실행할 수 있도록 보장합니다. 그러나 동기화는 비용이 많이 들고, 인스턴스가 생성된 후에는 필요하지 않습니다. 그때 double-checked locking 패턴이 등장합니다!

더블 체크드 락링

public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;

private DoubleCheckedSingleton() {}

public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}

이 패턴은 null인지 두 번 확인합니다: 첫 번째는 락을 걸지 않고, 두 번째는 락을 걸고 확인합니다. volatile 키워드는 여러 스레드가 instance 변수를 올바르게 처리하도록 보장합니다.

빌 퓰 싱글톤 구현

이제 제가 개인적으로 좋아하는 싱글톤 구현 방법을 소개해드리겠습니다. 이는 그 창시자 빌 퓰의 이름을 딴 것입니다:

public class BillPughSingleton {
private BillPughSingleton() {}

private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}

public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}

이 접근 방식은 정적 내부 클래스를 사용하여 인스턴스를 보관합니다. 동기화를 사용하지 않고도 스레드 안전하며, getInstance()가 처음 호출될 때 인스턴스를 lazy-load합니다.

싱글톤을 언제 사용할까요?

싱글톤은 다음과 같은 경우에 유용합니다:

  1. 공유된 자원을 관리할 때 (예: 데이터베이스 연결)
  2. 시스템 전체적인 작업을 조정할 때
  3. 자원 풀을 관리할 때 (예: 스레드 풀)

그러나 신중하게 사용해야 합니다! 싱글톤을 과도하게 사용하면 코드가 테스트와 유지보수가 어려워질 수 있습니다.

싱글톤 메서드

다음은 싱글톤 클래스에서 흔히 찾을 수 있는 메서드 표입니다:

메서드 설명
getInstance() 클래스의 단일 인스턴스를 반환합니다
readResolve() 직렬화 시 싱글톤 속성을 유지하기 위해 사용됩니다
clone() 일반적으로 CloneNotSupportedException을 던져 클론을 방지합니다

결론

와우! 오늘 우리는 많은 내용을 다루었습니다. 싱글톤이 무엇인지 이해하고, 여러 가지 싱글톤을 어떻게 구현하는지 배웠습니다. 이제 자바 프로젝트에서 이 강력한 디자인 패턴을 사용할 준비가 되었습니다.

기억해야 할 것은, 처음에 이야기한 Exclusive한 클럽처럼, 싱글톤은 신중하게 사용해야 합니다. 강력한 도구이지만, 큰 책임이 따릅니다!

자바 여정을 계속하면서 많은 흥미로운 개념을 만나게 될 것입니다. 계속 코딩하고, 학습하고, 가장 중요한 것은 즐겁게 하세요! 어쩌면 언젠가 다음 세대의 프로그래머들에게 싱글톤과 그 이상의 내용을 가르치게 될지도 모릅니다.

다음에 만날 때까지, 행복하게 코딩하세요!

Credits: Image by storyset