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 하도록 해야한다.