정의
- 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 주로 공통된 객체를 여러개 생성해서 사용하는 DBCP (DB Connection Pool)과 같은 상황에서 많이 사용된다.
- ex ) 리소스 공유 (JPA의 EntityManagerFactory 생성 ), 애플리케이션 설정, 로깅 및 분석, 애플리케이션 상태 관리 ( 사용자 인증 상태 )
구현
인스턴스를 제공하는 메서드와 인스턴스 변수 모두 Static으로 선언된 정적 변수 및 메서드
=> 기본생성자를 통해 생성할 수 없기 때문에 외부에서 인스턴스에 접근하려면 클래스 변수 및 메서드에 접근을 허용해야하기 때문에 정적타입으로 선언
// Eager Initialization 방식 : 한번만 미리 만들어두는, 가장 직관적이면서도 심플한 기법
// static final 이여서 멀티 쓰레드 환경에서도 안전하지만 리소스가 큰 객체인 경우 공간 자원 낭비 발생
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
// 생성자는 외부에서 호출하지 못하게 private 으로 지정한다
}
public static Singleton getInstance(){
return instance;
}
public void say() {
System.out.println("hi, there");
}
}
// Lazy initialization 방식 : 객체 생성에 대한 관리를 내부적으로 처리
// eager 방식을 보완했지만 thread safe 하지 않다는 단점
class Singleton {
// 싱글톤 클래스 객체를 담을 인스턴스 변수
private static Singleton instance;
// 생성자를 private로 선언 (외부에서 new 사용 X)
private Singleton() {}
// 외부에서 정적 메서드를 호출하면 그제서야 초기화 진행 (lazy)
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 오직 1개의 객체만 생성
}
return instance;
}
}
# enum은 애초에 멤버를 만들때 private으로 만들고 한번만 초기화 하기 때문에 thread safe
# 클래스 상속이 필요할때 enum 외의 클래스 상속은 불가능하다
enum SingletonEnum {
INSTANCE;
private final Client dbClient;
SingletonEnum() {
dbClient = Database.getClient();
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
public Client getClient() {
return dbClient;
}
}
public class Main {
public static void main(String[] args) {
SingletonEnum singleton = SingletonEnum.getInstance();
singleton.getClient();
}
}
장점
- 메모리 & 속도 측면 : 최초 한번의 new 연산자를 통해 고정된 메모리 영역을 사용하기 때문에 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다.
- 쉬운 데이터 공유 : 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근해서 사용할 수 있다. => 싱글톤 패턴에서는 멀티스레딩에서 동시성 문제가 발생하기 때문에 여러 기법을 사용해야한다.
단점
- 각 객체간의 결합도가 높아지고 변경에 유연하게 대처할 수 없다.
=> 단일 책임 원칙, 개방-폐쇄 원칙, 의존 역전 원칙에 어긋날 가능성이 있다
- 테스트가 힘들다
=> 인스턴스의 상태를 초기화 시켜줘야한다
멀티쓰레드 환경에서 싱글톤 문제를 해결하는 방법
- 정적 변수에 인스턴스를 만들어 바로 초기화 하는 방법
정적 변수는 객체가 생성되기 전 클래스가 메모리에 로딩할 때 만들어져 초기화가 한번만 실행된다.
synchronized 키워드를 통해 여러 쓰레드에서 동시에 접근하는 것을 막는 방법으로 해결
// thread safe : synchronized 성능 저하 발생 (overhead)
public class Singleton {
private static Singleton instance;
private Singleton() {}
// synchronized 메서드
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 인스턴스를 만드는 메서드에 동기화하는 방법
public interface Printer {
public void print(String input);
}
-----------------------
public class RealPrinter implements Printer {
private static Printer printer = null;
private RealPrinter() {
}
public synchronized static Printer getInstance() {
if (printer == null)
printer = new RealPrinter();
return printer;
}
@Override
public void print(String input) {
System.out.println(input);
}
}
레퍼런스)
💠 싱글톤(Singleton) 패턴 - 꼼꼼하게 알아보자
Singleton Pattern 싱글톤 패턴은 디자인 패턴들 중에서 가장 개념적으로 간단한 패턴이다. 하지만 간단한 만큼 이 패턴에 대해 코드만 던져주고 끝내버리는 경우가 있어, 어디에 쓰이는지 어떠한 문
inpa.tistory.com
'Java' 카테고리의 다른 글
[Java] Stream.toList 와 Stream.collect(toList()) 차이에 대해서 (1) | 2023.10.30 |
---|---|
[Java] 객체지향 프로그래밍 (0) | 2023.10.19 |