1. 목적
- 클래스의 인스턴스를 하나만 생성하고 전역적으로 접근가능하게 하는 디자인 패턴
2. 사용 이유
- 요청이 많은 트래픽의 사이트의 경우 객체를 계속 만들게 될경우 메모리 낭비가 심하기 때문이다.
3. 사용 예시
-
데이터베이스 연결
-
각종 설정
-
스프링의 핵심기능
코드
public class Singleton { // 클래스의 유일한 정적 변수 private static Singleton singletonInstance; // private 생성자를 통해 외부에서 객체 생성을 막음 private Singleton() { } // 싱글톤 인스턴스를 반환하는 정적 메서드 public static Singleton getInstance() { if (singletonInstance == null) { singletonInstance = new Singleton(); } return singletonInstance; } // 확인용 메시지 public void showMessage(){ System.out.println("싱글톤 출력~~~"); } public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); singleton1.showMessage(); singleton2.showMessage(); System.out.println("------- 같은 객체인지 확인 --------"); System.out.println(singleton1 == singleton2); if (singleton1 == singleton2) { System.out.println("singleton1 과 singleton2 는 같은 인스턴스"); } else { System.out.println("singleton1 과 singleton2 는 다른 인스턴스"); } } }
결과
4. 의문
- 싱글톤은 한개의 객체를 가지는데 멀티쓰레드환경에서 동시적으로 요청이 들어오면 위험한거 아닌가?
5. 해결
- 싱글톤은 상태를 가지면 안된다.
- 상태를 가지면 안된다는 말이 무엇이지?
5-1. 문제가있는 싱글톤
@Component
public class StatefulService {
private int price; // 상태를 저장하는 필드 (문제 발생 가능)
public void order(String user, int price) {
System.out.println(user + " 주문 금액: " + price);
this.price = price; // 문제 발생: 상태를 저장
}
public int getPrice() {
return price;
}
}
결과
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 같은 싱글톤 인스턴스를 가져옴
StatefulService service1 = context.getBean(StatefulService.class);
StatefulService service2 = context.getBean(StatefulService.class);
// 사용자 A가 10000원 주문
service1.order("사용자 A", 10000);
// 사용자 B가 20000원 주문
service2.order("사용자 B", 20000);
// 사용자 A의 주문 금액 확인
int price = service1.getPrice();
System.out.println("사용자 A의 주문 금액: " + price); // 예상: 10000, 실제: 20000
}
5-2 상태를 제거한 싱글톤
@Component
public class StatelessService {
public int order(String user, int price) {
System.out.println(user + " 주문 금액: " + price);
return price; // 상태 저장 없이 바로 반환
}
}
결과
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
StatelessService service = context.getBean(StatelessService.class);
// 사용자 A 주문
int userAPrice = service.order("사용자 A", 10000);
// 사용자 B 주문
int userBPrice = service.order("사용자 B", 20000);
// 주문 금액 출력
System.out.println("사용자 A 주문 금액: " + userAPrice); // 10000
System.out.println("사용자 B 주문 금액: " + userBPrice); // 20000
}
5-3 정리
- 상태를 가진다는것은 현재 상태나 동작에 영향을 미치는 데이터를 의미하며 5-1 의 price 와 같이 필드에 객체 내부의 데이터를 가지고있는 것이다.
- 이전요청의 결과를 내부에 저장하여 다음 요청에 영향을 미치기 때문에 싱글톤은 상태를 가지지 않도록(stateless) 설계 , 상태를 저장하지 않고 바로 return 하도록 해야한다.