정의
하나의 클래스에 대한 인스턴스를 하나만 가진다.
어디에 쓸까?
무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 Singleton으로 만들어야 한다.
예를 들어 서버의 각 로직을 담당하는 오브젝트들(controller, service 등)이 클라이언트에서 요청이 들어올 때마다 새로 생성된다고 생각해보자. 클라이언트의 요청이 증가할수록, 로직을 담당하는 오브젝트들의 개수도 기하급수적으로 늘어날 것이다
Spring 서버 환경에서 오브젝트(bean)들을 Singleton으로 생성한다.
싱글톤 패턴 In JAVA
class Singleton {
private static class singleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static synchronized Singleton getInstance() {
//synchronized 레이스 컨디션일경우에 한쓰레드만 이 메서드에 접근가능하게
return singleInstanceHolder.INSTANCE;
}
}
public class HelloWorld{
public static void main(String []args){
Singleton a = Singleton.getInstance();
Singleton b = Singleton.getInstance();
System.out.println(a.hashCode());
System.out.println(b.hashCode());
if (a == b){
System.out.println(true);
}
}
}
/*
705927765
705927765
true
*/
싱글톤 패턴을 적용한 클래스는 보통 전역적으로 사용하기 때문에, 상태를 가진 객체를 Singleton으로 만들면 안된다.
전역 클래스 변수는 곧, 멀티 스레딩 환경에서 오염이 가능해진다는 것을 의미한다. 단, 메서드 안에 로컬 지역변수는 쓰레드 별로 생성 되기 때문에 괜찮다.
추가사항
그렇지만 좋은 개발자가 되기 위해선 효율적인 코드에 대해서 고민을 해 봐야 한다. 메서드에 Singleton 클래스의 getInstance() 메서드에 synchronized 키워드를 추가하는 건 역할에 비해서 동기화 오버헤드가 심하다고 생각한다.
volatile키워드
non-volatile 변수는 기본적으로 cpu 캐싱이 되고 이 변수를 수정했을때 main memory에 언제 반영될지 모르기 때문에 non-volatile변수는 항상 최신 state임을 보장하지 않는다.
최선의 JAVA Singleton 패턴(LazyHolder)
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
Singleton은 왜 안티패턴이라 불리는가?
1. 객체지향과 맞지 않다. (SOLID원칙을 위반)
- 싱글톤 == 전역상태를 만들 수 있다.(아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 지양되어야 할 모델이다.)
- 싱글톤은 private 생성자를 갖고 있기 때문에 상속할 수 없다.
- static 필드와 메소드를 사용하기 때문에 다형성같은 객체지향의 특징이 적용되지 않는다
2. TDD가 어렵다.
미리 생성된 하나의 인스턴스를 가지고 독립적인 테스팅 환경을 만들기 매우어렵다.(unit test시)
클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다. 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 가짜 구현(mock object)으로 대체할 수 없기 때문이다.
3. 서버 환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
생성자를 private하게 두었어도 reflection을 통해 하나 이상의 오브젝트가 만들어질 수 있다. 또한 여러개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생기기 때문에 싱글톤으로서의 가치가 떨어진다.
4. 의존성이 높아진다.
SOLID 원칙의 대부분은 인터페이스 설계와 관련이 되어있다.
인터페이스 설계의 장점
- 의존성을 Interface에 두면 실제 구현 클래스의 구현이 변경되어도 이를 사용한 코드는 큰 영향을 받지 않는다. 그렇기 때문에 SOLID원칙을 지키기 위해서는 인터페이스로 설계하는게 좋은 설계이다.
하지만 Singleton을 사용하는 경우 대부분 인터페이스가 아닌 구현 클래스의 객체를 미리 생성해놓고 정적 메소드를 이용하여 구현하게 된다. Singleton을 사용하는 곳과 Singleton Class 사이에 의존성이 생기게 되고, 이는 결합도를 높이는 행위로, 수정 및 단위테스트의 어려움이 생기게 된다.
그럼 Spring의 Bean은 싱글톤이 아닌가? 스프링은 안티패턴을 쓰고있는건가?
> Spring은 Singleton registry 라는 것을 이용하여 싱글톤을 구현한다.
Singleton Registry
스프링이 직접 싱글턴 객체를 만들고 관리하는 기능을 제공하는 것을 의미한다. 이 구현 방법은 static 메서드와 private 생성자를 이용하여 single object로 생성됨을 강제하지 않는다. 단지, 평범한 java class를 singleton으로 활용하게 해준다.
이 말인 즉슨, 평범한 public 생성자를 가진 자바 클래스를 싱글턴으로 활용할 수 있게 만들어준다.
그렇게 할 수 있는 이유는 클래스의 제어권을 IoC방식의 컨테이너에게 넘기면 해당 컨테이너가 객체 생성에 대한 모든 권한을 가질 수 있기 때문 이다. 따라서 객체가 싱글턴으로 존재할 수 있게 관리할 수 있다. IoC 컨테이너의 제어 역할을 통해서 빈을 싱글톤으로 만들 수 있는 것이다.
'개발' 카테고리의 다른 글
| [Kotlin] 컬렉션(Collection) vs 시퀀스(Sequence): 함수형 API와 동작 원리 심층 분석 (0) | 2025.12.08 |
|---|---|
| 람다에 대해서 알아보자 - 1편 (탄생배경, 변수캡쳐, 함수타입) (0) | 2025.11.26 |
| Index는 왜 B-Tree를 사용하나 (0) | 2025.03.16 |
| Ajax에 대해서 알아보자 (0) | 2025.03.02 |
| 간략하게 알아보는 docker 개념 (0) | 2025.02.16 |