Develop Study/Spring
(25.04.03) Singleton Pattern & Spring Bean
면접 준비와 그리고 채용공고에서 보듯이 싱글톤 패턴을 통한 개발을 할 수 있는지 또는 이에 대한 이해가 있는지를 보기도 해,
한번 더 공부하기 위해서 찾아서 정리를 했다.
기본적으로 Spring 의 Bean이 싱글톤 패턴이라는 것으로만 알고 있었지만, 실제로 Singleton Pattern과는 조금 다른 점이 있고,
그리고 특히, Spring Bean을 싱글톤 패턴으로 개발하는 것이 목적이 아닌 DI를 목적으로 두기 때문에,
Singleton Pattern에 대해서 한번 찾아보고 공부했다.
싱글톤 패턴 Singleton Pattern
- Application 내에서 객체를 단 하나만 생성하고, 이를 공유하는 패턴
- Application 상태를 유지 또는 자원을 전역에서 공유 (또는 logging 등)
- 일종의 전역 변수처럼 활용하는 방식
- 제한된 Resource의 효율적인 사용과 관리로 메모리 낭비를 방지 가능
- 데이터 일관성 유지 가능
- 단, 멀티쓰레드 환경에서는 동기화 이슈가 있을 수 있기 때문에, 패턴 내 이를 처리하는 과정이 필요!
사용 예
- 공통 설정값 관리 : 설정 파일, DB 연결 상태
- 공유 리소스 관리 : 로깅, 캐싱, Thread Pool 관리
기본 Singleton 패턴 구조
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
- 생성자를 private으로 바꿔서, 전역에서 객체를 새롭게 생성해서 쓸 수 없도록 하고, 오직, getInstace를 통해 생성
- 단, 멀티쓰레드 환경에서 여러개의 instance가 생성될 가능성
- Race Condition 경합 상태가 충분히 발생이 가능
Double-checked Locking 패턴 활용
- 맣이 사용하는 구조
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile
- 일종의 제어자 중 Java 에서 멀티쓰레드 환경에서, 변수가 여러 쓰레드한테 공유가 될 때, 해당 변수의 최신 값을 모든 쓰레드가 즉시 확인하게 할 수 있음
- CPU 에서 확인하는게 아닌, 메인 메모리에서 직접 확인을 하는 방식 → 여러 쓰레드가 바로 확인
- 제어자를 통해 멀티쓰레드 환경에서 변수 값이 완전하게 초기화가 되었는지 확인 후에, 다른 쓰레드가 접근하게 하도록 보장도 함
- 단, 원자성(Atomicity)를 보장하지는 않음! → 쓰레드 단위지 데이터 단위로 작동이 아니므로
- READ→UPDATE→WRITE 과 같은 count++ 같은 연산도 동기화가 되지 않기 때문에, 경합 발생이 충분함
- 단순한 READ / WRITE 연산이어야
- 위의 JVM 상에서 instance 가 초기화된 상태여야 다른 쓰레드가 접근할 수 있도록 할 수 있음
- volatile 제어자의 연산 경합이 있을 수 있기 때문에 로직 내에 synchronized 를 적용해서 비관적 락을 걸어서 해당 블록실행 → 인스턴스 실행 할 수 있도록 한번 더 작동 시켜야함
- 결론적으로 하나의 객체만 작동될 수 있도록 하게 하는 것
Bill Pugh Singleton 패턴 활용
public class Singleton {
private Singleton() {}
private static class SingletonHelper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
- Lazy Loading (Lazy Initialization)이 가능
- 해당 SingletonHelper 클래스가 로드가 될 때, 최초로 인스턴스를 하나만 생성 → 이후에 getInstance를 할 때마다 이 인스턴스를 반환하는 방식
- synchronized 미사용
- Thread-Safe 가 그래도 보장되는 패턴
- 모든 Singleton 방식 중 가장 메모리 효율과 성능 최적화가 뛰어남
- synchronized를 사용하지 않았기 때문
SingletonHelper
- Static Nested Class
- inner 클래스와 구분됨
- outer 클래스에 종속된 것이 아닌, 어떠한 static 멤버처럼 독립적으로 활용
- Singleton 인스턴스를 안전하게 생성하는 클래스
- static 제어자를 활용해서 최초 getInstance 실행 시, Singleton 객체가 생성된 상태로 해당 INSTANCE 객체가 유지 → getInstance 할때마다 똑같은 객체만 불러오게 됨
- Java ClassLoader를 일종의 Synchronized 로 활용하는 것
- JVM 이 계속적으로 동기화를 진행
ex) Singleton DB Connection Manager
- DB의 연결, 해제를 담당하는 Manager 클래스
// Bill Pugh Singleton 패턴
public class DatabaseConnectionManager {
private Connection connection; // JDBC API 제공 인터페이스 따로 정의 할 필요 X, 연결되지 않은상태는 null 상태
private DatabaseConnectionManager() {}
private static class DatabaseHelper {
private static final DatabaseConnectionManager INSTANCE = new DatabaseConnectionManager();
}
public static DatabaseConnectionManager getInstance() {
return DatabaseHelper.INSTANCE;
}
// 연결 메서드
public synchronized void connect() {
if (connection != null ) {
System.out.println("DB is already connected");
}
if (connection == null) {
try {
String url = "jdbc:mysql://localhost:3306/database"; // 이름은 database로 임의로 가정
String user = "root";
String password = "password"; // 임의로 가정
connection = DriverManager.getConnection(url, user, password);
System.out.println("DB Connected");
} catch (SQLException e) {
throw new RuntimeException("Faild to connect", e);
}
}
}
// 연결 해제
public synchronized void disconnect() {
if (connection != null) {
try {
connection.close();
connection = null;
System.out.println("DB Disconnected");
} catch (SQLException e) {
throw new RuntimeException("Failed to disconnect DB", e);
}
}
}
}
public class Test1 {
public void run() {
DatabaseConnectionManager dbManager = DatabaseConnectionManager.getInstance();
dbManager.connect();
}
}
public class Test2 {
public void run() {
DatabaseConnectionManager dbManager = DatabaseConnectionManager.getInstance();
dbManager.connect();
}
}
public class Main {
public static void main(String[] args) {
Test1 test1 = new Test1();
Test2 test2 = new Test2();
test1.run(); // 첫 번째 클래스에서 DB 연결 시도 -> 성공 "DB Connected"
test2.run(); // 두 번째 클래스에서 DB 연결 시도 -> 이미 작동된 상태 "DB is already connected"
}
}
- dbManager가 Test1 Test2 클래스에 따로 정의가 되었더라도, Singleton 패턴이기 때문에 연결이 계속 유지가 되어 Main 에서 test2가 run() 해도 이미 connection이 유지가 되고 있는 상태
Spring Bean 과 Singleton Pattern
Spring Bean | Singleton Pattern | |
객체 생성 시점 | 기본적으로 Eager Loading (즉시 로딩) - Spring 컨테이너가 시작이 될 때, 즉시 생성하므로 |
일반적으로 Lazy Loading (지연 로딩) - 최초 객체가 생성될 때 로딩 |
객체 관리 주체 | Spring 컨테이너가 관리 | 애플리케이션 자체에서 관리 |
생성 방식 | @Component, @Bean을 사용하여 자동 등록 | private static instance를 사용하여 직접 관리 |
사용 범위 | 주로 DI(의존성 주입)용 객체 | 주로 전역에서 하나만 존재해야 하는 객체 |
멀티쓰레드 환경 | Spring이 알아서 Singleton을 보장 | synchronized 등으로 직접 관리 필요 (Bill Pugh Singleton 제외) |
확장성 | AOP, Proxy 등을 적용 가능 | 확장성이 떨어짐 |
- 단, @Lazy 사용시 필요에 따라 Spring 의 지연로딩도 가능
- Spring에서는 구체적으로 Singleton을 설정할 필요가 없음
- Spring은 주입이 목적이기 떄문에 일반 Singleton Pattern은 강한 결합
Bean Scope (참고)
- Spring 에서는 Bean의 Lifecycle을 조절하는 Scope가 있음 → 하나의 인스턴스를 계속 유지시키는 @Scope("singleton") 기본
@Scope | 인스턴스 생성 시점 | Lifecycle | 예시) (GPT 활용) |
singleton | 스프링 컨테이너 시작 시 1번 | 스프링 컨테이너가 관리 | 일반적인 서비스, DAO 등 |
prototype | getBean() 호출 시마다 | 클라이언트가 직접 관리 | 매번 새로운 객체가 필요한 경우 |
request | HTTP 요청마다 1번 | 요청이 끝나면 소멸 | 요청마다 새로운 상태가 필요한 경우 |
session | 세션당 1번 | 세션 종료 시 소멸 | 사용자별 데이터 유지 |
application | 서버 시작 시 1번 | 서버 종료 시 소멸 | 전역 설정, 공통 데이터 |
참고자료
Bean Scopes :: Spring Framework
The request, session, application, and websocket scopes are available only if you use a web-aware Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you use these scopes with regular Spring IoC containers, such as the ClassPath
docs.spring.io
'Develop Study > Spring' 카테고리의 다른 글
(25.04.22) Spring Security 인증 인가를 통한 Login 기능 - Filter & JWT에 대해 (0) | 2025.04.22 |
---|---|
(25.04.16) HATEOAS & Spring Boot 에서 적용하기 (0) | 2025.04.16 |
(25.03.13) Java Spring JPA @Converter & AttributeConverter (0) | 2025.03.13 |
(25.02.18) Spring Boot 의 MongoDB 연결 (0) | 2025.02.18 |
(24.10.14) Swagger & Spring REST Doc (2) | 2024.10.14 |