어플리케이션이 시작될 때 어떤 클래스가 최초 한번만 메모리를 할당하고(static) 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴이다.
즉, 메모리 낭비를 방지할 수 있고, 전역이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽다!
public class Singleton {
// static 으로 선언하여 전역에서 단독으로 존재하는 인스턴스 보장
private static Singleton instance;
// private 생성자로 외부에서 객체 생성을 막음
private Singleton() {
}
// 외부에서는 getInstance() 로 instance 를 반환
public static Singleton getInstance() {
// instance 가 null 일 때만 생성
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
instance = new Singleton();
처럼 구체 클래스에 의존하게 되므로, DIP와 OCP 원칙을 위반하게 되어 유연성이 매우 떨어진다.스프링 컨테이너에서는 싱글턴 패턴을 적용하지 않아도 등록한 스프링 빈을 싱글톤으로 관리한다. 이러한 기능 덕분에 싱글톤 패턴의 모든 단점을 해결하고 객체를 싱글톤으로 유지할 수 있다.
스프링 컨테이너에서 싱글톤으로 관리된다고 하여도, 만약 객체의 상태가 유지된다면(Stateful) thread-safe 하지 못하게된다. 즉, 무상태(Stateless)로 설계되어야 한다.
public class StatefulService {
private int price; //상태를 유지하는 필드
public void order(String name, int price) {
this.price = price; //특정 클라이언트에 의해 상태값이 변경된다.
}
public int getPrice() {
return price;
}
}
StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
//ThreadA : A사용자 10000원 주문
statefulService1.order("userA",10000);
//ThreadB : B사용자 20000원 주문
statefulService2.order("userB",20000);
//ThreadA : 사용자 A주문 금액 조회
int price = statefulService1.getPrice();
//ThreadA : 사용자 A는 10000원 기대했지만, 기대와 다르게 20000원 출력
→ 공유하는 필드가 있으면 안된다!!! (상태가 유지됨)
→ 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
→ 스프링에서는 보통 불변 객체를 빈으로 등록하여 방지한다던가, 가변 객체가 존재하더라도 synchronized 키워드나 concurrent 패키지의 클래스들을 사용하여 동시성 문제를 해결했을 것이다. 또한 스프링 빈 사이의 데이터를 주고받을 때에는 스프링 빈의 상태를 변경 시키는 것이 아니라 메소드의 파라미터를 이용했을 것이다. 즉, 스프링이 지향하는 객체지향적 관행을 따라하다 보니 자연스럽게 Thread-safe하게 개발이 된다.
https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/
https://alwayspr.tistory.com/11