티스토리 뷰
싱글톤 패턴(Singleton Pattern)
싱글톤 패턴(Singleton Pattern)은 객체지향 프로그래밍에서 자주 사용되는 디자인패턴입니다.
소프트웨어 디자인 패턴 중 하나인 싱글톤 패턴의 핵심은 클래스의 인스턴스를 딱 하나만 생성하여 애플리케이션 전체에서 공유한다는 것입니다. 생성자가 여러 번 호출이 되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴합니다.
이런 싱글톤 패턴을 사용하면 메모리 사용을 줄일 수 있으며, 데이터의 공유가 편해집니다.
이 글에서는 싱글톤 패턴이 무엇인지 그리고 자바를 이용하여 싱글톤을 구현해보고 장단점을 보겠습니다.
스프링에서 싱글톤 패턴을 어떻게 사용하는지까지 예제와 그림을 통해서 완벽하게 이해하는 시간을 갖도록 하겠습니다.
싱글톤이 사용되는 이유는?
개발자분들은 보통 서비스를 운영하다보면 동일한 요청이 서로 다른 클라이언트로부터 동시에 들어오는 경우가 잦습니다.
요청이 들어오면 객체를 만들어서 메모리를 사용하게 되는데, 만약 동일한 요청들을 전부 다른 메모리 공간에 할당시켜 각각 응답해 주게 되면 메모리 공간이 남아나질 않을 것입니다.
아래 코드는 싱글톤 패턴을 적용하지 않은 코드 입니다.
동일한 요청에 대해서 각각 서로 다른 객체들을 만들어서 클라이언트에게 응답해주고 있습니다.
public class NoSingletonConfig {
// service() 호출 될 때마다 새로 생성해서 반환해준다.
public Service service() {
return new Service;
}
}
public class Service {
//service 자기 자신 호출
public void serviceMethod() {
System.out.println(this);
}
}
public class Main {
public static void main(String[] args) {
NoSingletonConfig config = new NoSingletonConfig();
//1. 호출 할 때 마다 객체를 생성
Service service1 = config.service();
//2. 호출 할 때 마다 객체를 생성
Service service2 = config.service();
//3. 호출 할 때 마다 객체를 생성
Service service3 = config.service();
service1.serviceMethod();
service2.serviceMethod();
service3.serviceMethod();
}
}
service1, 2, 3의 객체를 출력해 보면 다음과 같이 서로 다른 주소값을 가진 객체인 것을 확인할 수 있습니다.
위의 코드는 아래 사진과 같이 클라이언트가 서비스를 호출할 때마다 매번 새로운 객체를 생성하여 클라이언트에게 반환을 해줍니다.
지금처럼 3개의 새로운 객체는 전혀 지장이 없겠지만, 실제로 서비스를 운영하다 보면 1초에 몇 만 번의 호출이 일어날 때도 있다고 합니다. 그렇다면 싱글톤을 사용하지 않고 위처럼 구현한다면 1초에 몇 만개의 객체가 생성되는 것이죠.
메모리를 과하게 사용하여 장애를 일으킬지도 모릅니다.
그렇기 때문에 동일한 요청(service)에 클래스의 인스턴스를 딱 1개만 생성하여 관리하도록 합니다.
아래 코드는 자바를 이용하여 싱글톤을 구현한 예제입니다.
public class SingletonConfig {
// service() 호출할 때마다 새로 생성하지 않고 고정된 Service 반환
public Service service() {
return Service.getInstance();
}
}
public class Service {
//1. static 영역에 객체를 딱 1개만 생성한다.
private static final Service service = new Service();
//2. static method 를 통해서 객체를 생성하도록 한다.
public static Service getInstance() {
return service;
}
//3. private 생성자를 통해서 외부에서 new로 객체를 생성하는 것을 막는다.
private Service() {
}
//4. this 를 사용하여 자기 자신 호출
public void serviceMethod() {
System.out.println(this);
}
}
public class Main {
public static void main(String[] args) {
SingletonConfig config = new SingletonConfig();
Service service1 = config.service();
Service service2 = config.service();
Service service3 = config.service();
service1.serviceMethod();
service2.serviceMethod();
service3.serviceMethod();
}
}
service1, 2, 3의 객체를 출력해 보면 다음과 같이 서로 같은 주소값을 가진 객체인 것을 확인할 수 있습니다.
위 코드를 분석을 해보면,
1. static 영역에 객체 인스턴스를 미리 생성해서 올려둔다.
static 키워드를 사용함으로써 static 영역에 객체 인스턴스를 올려둡니다.
static 영역에는 하나의 객체가 고정된 메모리값을 사용하기 때문에 애플리케이션 실행 중에는 값을 변경할 수 없습니다.
2. static 메서드를 사용하여 객체 인스턴스를 조회한다.
기존에 Config 파일에서 싱글톤을 사용하지 않을 때는 service 메서드에서 return new Service(); 를 통해 객체를 매번 생성해서 반환해 주었지만, 싱글톤을 사용하는 Config 파일에서는 retrun Service.getInstance() 메서드를 통해 static 영역에 이미 생성되어 있는 Service 객체를 반환하여 줍니다.
3. private 사용하여 생성자를 막는다.
이 코드는 클래스 외부에서 인스턴스를 생성하는 것을 방지하기 위해 생성자를 private으로 설정해 줍니다.
싱글톤 패턴의 문제점
이렇게 싱글톤을 구현하면 메모리 낭비를 막을 수 있다는 장점이 있지만, 위 싱글톤을 구현하기 위한 코드를 보면 알다시피 싱글톤 패턴을 구현하는 코드 자체가 많아집니다.
싱글톤 패턴을 사용하는 다른 객체들 간의 결합도(의존성)이 높아지기 때문에 객체 지향 설계 원칙에 위배됩니다. 즉, 클라이언트가 구체 클래스에 의존하게 되는 것이죠.
내부 설계를 변경하거나 초기화하기가 어렵고, private 생성자를 사용하기 때문에 자식 클래스를 만들기 어렵기도합니다.
결론적으로는 유연하지 않다는 큰 단점이 존재합니다.
그럼 뭐냐? 쓰지 말라는 것이냐???
이러한 문제점들을 스프링에서 해결해 줍니다.
스프링에서의 싱글톤
스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리해 줍니다.
이러한 기능 덕분에 싱글톤 패턴의 모든 단점을 해결하고 객체를 싱글톤으로 유지할 수 있습니다.
스프링 통해 싱글톤 구현
@Configuration
public class SingletonConfig {
@Bean
public Service service() {
return new Service;
}
}
public class Service {
public void serviceMethod() {
System.out.println(this);
}
}
@ComponentScan
public class Main {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SingletonConfig.class);
Service service1 = ac.getBean(Service.class);
Service service2 = ac.getBean(Service.class);
Service service3 = ac.getBean(Service.class);
service1.serviceMethod();
service2.serviceMethod();
service3.serviceMethod();
}
}
위 코드의 출력 결과를 보시면 동일한 객체가 반환되는 것을 확인할 수 있습니다.
(싱글톤 패턴 적용)
따로 설명이 필요한 게 없을 정도입니다.
@Configuration, @Bean, @Component 애노테이션을 사용하여 스프링 컨테이너를 생성하고 Service 클래스를 빈으로 등록하면 스프링 컨테이너가 알아서 싱글톤으로 관리하여 줍니다.
스프링 컨테이너와 빈에 대해서 이해가 부족하시다면 아래 링크를 통해 공부해 보시는 것을 추천드립니다
싱글톤 방식의 주의점
자바를 통하여 싱글톤 패턴을 사용하든, 스프링에서 싱글톤 패턴을 관리해 주는 것을 사용할 때, 객체 인스턴스를 공유하기 때문에 객체 상태를 유지(stateful)하게 설계하면 안 됩니다.
아래 코드 같은 경우 price는 공유되는 필드이기 때문에 특정 클라이언트가 값을 변경하게 됩니다.
public class StatefulService {
private in price; // 상태를 유지하는 필드
public void order(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
}
아래 그림과 같이 클라이언트 A가 service에 price 값을 5000으로 설정했다고 가정해 봅시다.
그 이후에 price 값을 반환받기 전에 클라이언트 B가 price 값을 10000으로 변경하였습니다.
A와 B는 같은 객체를 바로 보고 있기 때문에 A가 price 값을 반환받으려고 한다면 10000원을 반환받게 됩니다.
이와 같은 큰 문제들(버그)이 발생할 수 있기 때문에 싱글톤을 사용할 때는 주의하여야 합니다.
출처(참고)
https://curiousjinan.tistory.com/8#5.%20Spring%EA%B3%BC%20Singleton%EC%9D%98%20%EA%B4%80%EA%B3%84-1
'Spring | Spring Boot' 카테고리의 다른 글
[Spring] HttpServletRequest 개요 및 기본 사용법 (0) | 2024.05.10 |
---|---|
[Spring] 빈 스코프(Bean Scope) 완벽하게 이해하기 (1) | 2024.04.30 |
[Spring] 빈 생명주기 콜백 - 꼬리에 꼬리를 물고 완벽 이해하기 (1) | 2024.04.24 |
[Spring] 스프링과 스프링부트(Spring Boot) (1) | 2024.04.20 |
[Spring] IoC(제어의 역전)와 DI(의존성 주입)의 완벽 이해 (0) | 2024.03.20 |
- Total
- Today
- Yesterday
- 요청데이터
- erd툴
- 인터페이스
- Servlet
- 타임리프 기본기능
- 스프링http
- HttpServletRequest
- 자바
- 인식안됨
- 스프링특징
- RequiredArgsConstruct
- 빈생명주기콜백
- 타임리프
- redirectattribute
- Java
- Thymeleaf
- Spring
- 인터페이스 추상클래스 비교
- 스프링
- erd editor
- 추상클래스
- HTTP요청
- 네이버지도크롤링
- 스프링 컨테이너
- 객체지향설계원칙
- 스프링 빈
- 요청매핑
- 크롤링
- Overloding
- 인터페이스 추상클래스 차이
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |