싱글톤 패턴

꾸준하게 달리기~·2023년 7월 5일
0

cs

목록 보기
7/16
post-thumbnail

싱글톤 패턴은, 디자인 패턴중의 한 종류이다.

디자인 패턴이란?

흔히들 프로젝트를 해봤으면,
MVC패턴 정도는 알고 있을거다.

MVC패턴도 디자인 패턴중의 하나이고,
디자인 패턴이란 객체 지향 프로그래밍 설계를 할 때
자주 발생하는 문제들을 피하기 위해 사용되는 패턴이다.

각설하고,

객체를 생성하는데 자원이 많이 든다.
그리고
여러개의 객체가 아닌 데이터 공유를 단 하나의 객체에서 해야한다.
(커넥션풀, 스레드풀 등과 같은 데이터. )

라는 문제가 생겼을때 어떻게 해야할까...?

이때 사용하는 디자인패턴중 하나가 싱글톤 패턴이다.
아래의 그림으로 예시를 들겠다.
사진출처 : https://refactoring.guru/ko/design-patterns/singleton

문을 여는 친구가 참조하는 클래스이고, 문을 의존성 주입, 안에 있는 친구를 싱글톤 객체라고 하자.

그럼 수많은 클래스에서 참조하더래도(문여는 수많은 친구들이 있더라도),
수많은 문이 열리더라도(의존성 주입을 여러번 수행하더라도),
안에 있는 친구(싱글톤 객체)는 단 하나이다.
약간 느낌이 오는가..?
그럼 설명을 시작하겠당.


싱글톤 패턴

싱글톤 패턴이란?, 그 예시

싱글톤 패턴이란,
하나의 인스턴스만 생성하여 재사용하는 디자인 패턴이다.

코드 예시는 아래와 같다.

실제로 생성되는 객체는 하나(아래의 number)이고,

최초로 생성된 이후에 호출된 생성자
(생성자 역할은 private 생성자가 아닌 getInstance() 매서드)는 이미 생성한 객체(number)를 반환한다.

public class Number {
    // 싱글톤이기 때문에, 넘버 라는 클래스의 객체가 단 한개만 존재해야 하므로 
    // private static으로 선언.
    // 현재는 number에 어떤 값을 할당해주지 않았다. 즉, null값
    private static Number number;
    

    // 생성자도 private로 선언하여 외부에서 객체 생성도 막아주어야 함.
    private Number() {
    }
    
    // 그렇다면, 해당 number 객체를 사용하기 위해서는 아래의 getInstance 매서드를 사용해야 한다.
    // number 객체가 null 이라면 생성해서 주고, 아니라면 기존의 number 객체를 return 한다.
    // 해당 매서드와 싱글톤 패턴의 단점 2번과 연결된다.
    public static Number getInstance() {
        if(number == null) {
            number = new Number();
        }
        return number;
    }

}

싱글톤 패턴 사용 이유는?

  • 객체 생성에 사용되는 메모리 낭비 방지
  • 싱글톤으로 구현한 인스턴스는 전역(static)이므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능

싱글톤이 사용되는 경우는?

  • 데이터베이스에서 커넥션풀, 스레드풀, 캐시, 로그 기록 객체 등등에 사용 (데이터 공유하는것이 효율적인것들.)

싱글톤 패턴의 단점은?

  • 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스 간의 결합도가 높아져 개방-폐쇄 원칙이 위배된다.
  • 멀티 스레드 환경에서 동기화 처리 하지 않았을 때, 인스턴스가 2개가 생성되는 문제 발생 가능
    (즉, 여러개의 스레드가 위의 getInstance() 매서드를 사용하면,
    동기성이 확보되지 않기 때문에, 인스턴스가 여러개 생성될 수 있다.
    그렇다면 하나의 객체만을 사용하는 싱글톤 패턴이 아니게 된다.)


멀티스레드 환경에서 안전한 싱글톤 만드는 방법 (위의 단점 2번의 해결법)

Lazy Initialization (지연 초기화)private static으로 인스턴스 변수 생성

public class Number {
    // getInstance의 synchronized 빼고는 위와 전부 똑같다.
    private static Number number;
    
    
    private Number() {
    }
    
    public synchronized static Number getInstance() {
        if(number == null) {
            number = new Number();
        }
        return number;
    }

}

synchronized 로 인해 여러 스레드가 동시에 접근하지 못하고 순차적으로 실행되기 때문에 동기화가 보장된다.

즉, 한번 생성이 되었다면,
getInstance() 안의
if(number ==null) 로직은 작동하지 않는다는 말.

하지만, synchronized는 큰 성능저하를 발생시키므로 권장하지 않는다.

Lazy Initialization + Double-checked Locking

public class Number {
    
    private static Number number;


    private Number() {
    }

    //해당 부분 이중 if문으로 변경
    public static Number getInstance() {
        if(number == null) {
            synchronized (Number.class){
                if(number == null){
                    number = new Number();
                }
            }
        }
        return number;
    }

}

조건문으로 인스턴스의 존재 여부를 확인한 다음 두번째 조건문에서 synchronized를 통해 동기화를 시켜 인스턴스를 생성하는 방법

하지만 이 방법 또한 추천되지 않음. (자바 5 이전에 깨짐.)

Initialization on demand holder idiom (holder에 의한 초기화)

public class Number {
    
    
    private Number() {
    }
        
    //여기부터
    //LazyHolder 클래스 추가
    private static class LazyHolder {
        public static final Number number = new Number();
    }

    public static Number getInstance() {
        return LazyHolder.number;
    }
    //여기까지 추가

}

getInstance() 매서드가 한번만 실행되어도,
LazyHolder.number이 호출되어 LazyHolder Class가 로딩되며
public static final Number number = new Number(); 가 실행되어
초기화가 진행된다.

JVM은 클래스 로딩을 수행할 때 이미 로드된 클래스는 다시 로드하지 않고,
이미 로드된 클래스의 정보를 사용한다.

즉, getInstance() 매서드가 실행되며
LazyHolder.number이 호출되고
LazyHolder Class가 로딩되는데, 단 한번만 로딩되므로
해당 클래스 안의
public static final Number number = new Number();
로직이 한번만 수행된다.

그렇게 여러개의 객체가 생성되는것을 막고, 싱글톤이 수행된다.

실제로 가장 많이 사용하는 일반적인 싱글톤 클래스 사용 방법이다.

profile
반갑습니다~! 좋은하루 보내세요 :)

0개의 댓글