싱글톤패턴(Singleton Pattern)
: 애플리케이션이 시작될때, 어떤 클래스가 최초 한번만 메모리를 할당(Static)
해당 메모리에 인스턴스를 만들어 사용(=하나의 인스턴스만 생성하여 사용)
싱글톤 패턴 특징
1) private를 선언하여 사용
2) 생성자에서 getInstance()를 사용하여 구현
3) 객체 생성에 쓰이는 메모리 영역의 낭비를 방지
인스턴스는 ‘전역’으로 구현하며, 다른 클래스의 인스턴스들이 데이터를 공유
ex) 커넥션풀, 스레드풀, 로그, 캐시
싱글톤 패턴 장점
전역 접근 : 애플리케이션 전역에서 하나만 존재하고 접근 가능
메모리 절약 : 새로운 인스턴스를 생성하지 않아 메모리 절약
싱글톤 패턴 단점
결합도 증가 : 전역에서 접근 사용하여 한 인스턴스에 의존
테스트 복잡성 : 한 인스턴스에 의존하여 결합도 증가로 테스트가 어려움
전역 접근 : 전역 접근 가능으로 무분별한 사용
싱글톤 패턴 구현 구조
Singleton |
Private 객체 |
Private 생성자() |
Public 객체 getInstance() |
1. 자기 자신의 이름의 객체를 가지고 있어야 함
2. 기본 생성자를 private로 생성
3. static 메소드로 인스턴스 객체 제공(new 사용 X)
예제
A클래스 싱글톤 객체 생성
public class Aside {
private SocketClient socketClient;
public Aside(){
// 기본생성자를 막아놓았으므로 기본생성자로 생성 불가
//this.socketClient = new SocketClient(); // 싱글톤 방식이 아닌 새로운 객체 생성 방법
this.socketClient = SocketClient.getInstance();
}
public SocketClient getSocketClient(){
return this.socketClient;
}
}
B클래스 싱글톤 객체 생성
public class Bside {
private SocketClient socketClient;
public Bside(){
// 기본생성자를 막아놓았으므로 기본생성자로 생성 불가
// this.socketClient = new SocketClient(); // default생성자의 private
this.socketClient = SocketClient.getInstance();
}
public SocketClient getSocketClient(){
return this.socketClient;
}
}
싱글톤으로 사용할 클래스
public class SocketClient {
/* 방식 */
// 1. 싱글톤은 자기자신을 객체로 가지고 있어야함
// 2. 기본(default)생성자를 private으로 생성을 외부로부터 막는다.
// 3. 자기 자신을 null로 가지고 있어야함
private static SocketClient socketClient = null;
// 객체 비교를 위해 Aside에서 새로운 new생성자 사용시 public으로 선언
private SocketClient(){
}
// static메소드를 통해 getInstance()메소드를 제공
// 객체는 자기자신을 리턴
// 어떠한 클래스든 접근 가능
// null 체크를 통해 객체 생성
// 최초의 한번만 생성
public static SocketClient getInstance(){
if(socketClient == null){
socketClient = new SocketClient();
}
return socketClient;
}
public void connect(){
System.out.println("Connect : SockectClient");
}
}
메인 출력(싱글톤)
public class Main {
public static void main(String[] args) {
/* 디자인 패턴 종류 */
/* 1. 싱글톤 패턴 */
sington();
/* 2. 어댑터 패턴 */
//adapter();
}
public static void sington() {
System.out.println("********** 싱글톤 패턴 시작 **********");
Aside aside = new Aside();
Bside bside = new Bside();
// 두개의 클라이언트의 객체가 같은지 확인
SocketClient a = aside.getSocketClient();
SocketClient b = bside.getSocketClient();
// 싱글톤방식
System.out.println("두개의 객체 비교 : ");
System.out.println("비교값 : " + a.equals(b)); // true
System.out.println("a : " + a);
a.connect();
System.out.println("b : " + b);
b.connect();
System.out.println("********** 싱글톤 패턴 끝 **********");
}
}
결과(기존new생성자)
********** 싱글톤 패턴 시작 **********
두개의 객체 비교 :
비교값 : false
a : com.design.pattern.singleton.SocketClient@1f32e575
Connect : SockectClient
b : com.design.pattern.singleton.SocketClient@279f2327
Connect : SockectClient
********** 싱글톤 패턴 끝 **********
결과(싱글톤)
********** 싱글톤 패턴 시작 **********
두개의 객체 비교 :
비교값 : true
a : com.design.pattern.singleton.SocketClient@1f32e575
Connect : SockectClient
b : com.design.pattern.singleton.SocketClient@1f32e575
Connect : SockectClient
********** 싱글톤 패턴 끝 **********
멀티스레드 환경의 싱글톤 패턴
싱글톤 패턴은 여러 스레드가 동시에 접근하는 경우 문제가 발생합니다. 그러므로 멀티 스레드 환경에서는 Thread Safe하지 않습니다.
예를 들어 A스레드가 getInstance()메소드에 접근하여 생성하려고 할 때 B스레드가 getInstance의 if문 조건을 통과하게 되어 문제가 발생할 수도 있습니다.
그래서 Thread Safe한 싱글톤 패턴의 여러가지 구현을 알아보자.
공통 메인 스레드 호출 코드
public class ThreadSafeTest {
public static void main(String[] args) {
Thread threadA = new Thread(()-> {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
});
Thread threadB = new Thread(()-> {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
});
threadA.start();
threadB.start();
}
}
1. synchronized 키워드 사용
getInstace() 메소드에 sychronized 키워드를 통해 여러 스레드가 동시 접근하려 할 때 동기화를 걸어 하나의 스레드만 접근가능하도록 합니다.
그러나, 동기화 작업(Lock) 때문에 성능 저하가 발생할 수 있습니다.
public class Singleton {
private static Singleton singleton= null;
private Singleton(){
}
/**
* synchronized 키워드 사용
* @return
*/
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
2. Double Checked Locking
초기화 할 때마다 동기화 작업을 수행하는 방법으로 volatile 키워드와 더블 체크로 통한 synchronized 키워드 활용하는 방법입니다.
volatile키워드는 JVM1.5이상에서만 사용
* volatile 키워드는 멀티 스레드 환경에서 자바 코드의 변수를 메인 메모리에 저장할 것을 명시하기 위해 사용한다는 정도만 알고 있자.
public class Singleton {
/**
* volatile 사용
*/
private static volatile Singleton singleton;
private Singleton(){
}
/**
* Double sychronized 사용
* @return
*/
public static synchronized Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
3. 이른 초기화(eager initialization)
객체 생성하는 비용이 크지 않은 경우 이른 초기화를 사용하여 적용합니다.
변수 선언과 동시에 초기화하여 싱글톤 패턴 구현, final 키워드를 통해 전역 상수로 공유
단, 애플리케이션 실행과 동시에 미리 생성해야하는 것이 단점으로 사용하지 않을 경우 자원 낭비에 해당됩니다.
public class Singleton {
/**
* 이른 초기화
*/
private static final Singleton SINGLETON_OBJ= new Singleton();
private Singleton(){
}
/**
* synchronized 사용
* @return
*/
public static synchronized Singleton getInstance(){
return SINGLETON_OBJ;
}
}
4. Bill Pugh Solution 사용 및 권장
static inner class를 사용하여 thread safe한 싱글톤 패턴 구현 방법입니다.
JVM의 ClassLoader에 의해서 로드될 때 내부적으로 실행되는 synchronized 키워드 사용 방법입니다.
public class Singleton {
private Singleton(){
}
private static class SingletonLoader{
private static final Singleton SINGLETON_OBJ =new Singleton();
}
public static Singleton getInstance(){
return SingletonLoader.SINGLETON_OBJ;
}
}
5. Enum 사용 및 권장
public enum Singleton {
SINGLETON_INS;
}
'Spring > 0. Design Pattern' 카테고리의 다른 글
5. 옵저버 패턴(Observer Pattern) (0) | 2024.06.14 |
---|---|
4. 팩토리 메소드 패턴(Factory method pattern) (0) | 2024.06.03 |
3. 프록시 패턴(Proxy Pattern) (0) | 2024.05.27 |
2. 어댑터 패턴(Adapter Pattern) (0) | 2022.09.09 |