본문 바로가기

웹 개발 한걸음

[Design Pattern] 싱글톤 패턴 배워보기

 

본 포스팅은

정보 제공용이 아닌

여러 개발자 블로그들의 자료를

바탕으로 보고 배운 것을 직접 정리해본 포스트 입니다.

 

 


 

    1. 싱글톤 패턴이란?    

 

  • 어플리케이션이 시작될 때 해당 클래스가 "최초 한번" 메모리를 할당하고(static) 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴이다.
  • 생성자가 여러번 호출되더라도 실제로 생성되는 객체는 하나뿐이고 최초 생성 이후에 호출된 생성자는 최초의 생성한 객체를 반환한다. 

 

 


 

    2. 왜 사용하는 것인가?    

어떤 클래스에서 호출하든 하나의 특정 인스턴스만 호출된다.

 

  • A라는 클래스가 있다하자.
  • B클래스와 C클래스에서 필요에 의해 A클래스의 생성자를 호출해 인스턴스를 생성하게 되면  B와 C에서 생성된 A클래스가 메모리에 각각 올라가버린다.
  • 하지만 이 싱글톤 패턴을 사용하면 A 객체 생성을 단 한 번으로 제한하여 같은 객체의 인스턴스가 무분별하게 복제되는 경우를 방지할 수 있게 된다. 

 


 

    3. 싱글톤 패턴의 장점    

  • 1) 메모리 낭비를 방지할 수 있다.
  • 2) 싱글톤으로 만들어진 클래스와 다른 클래스의 인스턴스들의 데이터 공유가 쉽다.
  • 3) 인스턴스가 절대적으로 한개만 존재하는 것을 보증하기에 개발 시 실수를 줄일 수 있다.
  • 4) 싱글톤 객체를 사용하지 않는 경우 인스턴스를 생성하지 않는다.
  • 5) 싱글톤을 상속시킬 수 있다.

 

    4. 싱글톤 패턴의 단점    

1. 전역변수보다 사용하기가 불편하다.

2. 싱글톤의 역할이 커질수록 결합도가 높아져 객체 지향 설계 원칙에 어긋날 수 있다.

3. 멀티쓰레드 환경에서 컨트롤이 어렵다.

4. 객체의 파괴 시점을 컨트롤하기 어려울 수 있다.

 

 


 

     5. 싱글톤 구현하기     

** 기본 싱글톤 패턴

//기본 싱글톤 패턴

public class Singleton {
    // 단 1개만 존재해야 하는 객체의 인스턴스, static 으로 선언
    private static Singleton instance;

    // private 생성자로 외부에서 객체 생성을 막음
    private Singleton() {}

    // 외부에서는 getInstance() 로 instance 를 반환
    public static Singleton getInstance() {
      // instance 가 null 일 때만 생성
      if (instance == null){
          instance = new Singleton();
      }
      return instance; 
    } 
}

 

  • 1) 외부에서 생성할 수 없게 생성자를 private으로 선언한다.
  • 2) getInstance() 메서드로만 instance에 접근이 가능하게 만든다.
  • 3) instance는 static으로 선언해두고
  • 4) getInstance가 최초로 호출될 때는 null이므로 생성해주고 이후에는 생성해둔 instance를 반환하게 하여
  • 5) 여러 번 호출하더라도 최초 생성한 instance만 반환하게 한다.

 

하지만 위와 같은 늦은 방법은 멀티 스레드 환경에서 Tread-Safe를 보장해주지 않는다.

예를 들어 두 스레드가 동시에 싱글톤 인스턴스에 접근할 때 생성이 안된 것을 확인하고 생성시 동시에 중복으로 생성될 수 있는 문제가 있다. 이런 문제를 해결하기 위해 java에서 스레드를 지원하는 Synchrozied를 사용한다.

 

** synchrozied를 사용한 싱글톤 패턴

//synchrozied를 사용한 싱글톤 패턴

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    // synchronzied 스레드 동기화를 사용하며 멀티 스레드 환경에서의 동시성 문제 해결
    public static synchronzied Singleton getInstance() {
      if(instance  == null) {
         instance  = new Singleton();
      }
      return instance;
    }
}

 

  •  synchrozied를 사용하여 여러 스레드가 getInstance() 메서드에 동시에 접근하는 것을 방지할 수 있다.
  • 하지만 synchrozied의 단점은 속도가 매우 느려져 성능이 저하된다는 점이다. 

** double-check를 사용한 싱글턴 패턴

// Double-check를 사용한 싱글톤 패턴

public class Singleton {
    private volatile static Singleton instance;

    private Sigleton() {}
    
    // Double check
    public Singleton getInstance() {
     // instance 가 null 인 경우
     if(instance == null) {
         // synchronized 를 적용한 후, 한번 더 instance 가 null 인지를 체크
         synchronized(Singleton.class) {
            if(instance == null) {
               instance = new Singleton(); 
            }
         }
      }
      return uniqueInstance;
    }
}

 

Double-check는 말 그대로 두 번 체크 하는 방법으로 synchrozied를 사용하기 전에 instance가 null인지를 먼저 체크한 후 synchrozied를 사용하여 스레드를 동기화한 후 null인지 한번 더 체크하는 방법이다. 이러면 synchrozied의 속도 저하를 어느정도 개선이 가능하다.

 

** holder를 사용한 싱글톤 패턴

// holder를 사용한 싱글톤 패턴

public class Singleton {

    private Singleton() {}
    
    // private static inner class 인 LazyHolder
    private static class LazyHolder() {
        // LazyHolder 클래스 초기화 과정에서 JVM 이 Thread-Safe 하게 instance 를 생성
        private static final Singleton instance = new Singleton();
    }

    // LazyHolder 의 instance 에 접근하여 반환
    public static Singleton getInstance() {
        return LazyHolder.instance;
    }
    
}

 

  • 이 싱글톤 패턴은 JAVA 진영에서 가장 많이, 일반적으로 사용하는 패턴이다.
  • 클래스 안에 클래스(holder)를 두어 JVM의 Class loader 매커니즘과 class가 로드되는 시점을 이용한 방법.
  • 개발자가 직접 동기화 문제에 코드를 작성하여 만듦으로 생기는 문제를 JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용하여 싱글턴 초기화 문제의 책임을 JVM에 떠 넘기는 패턴이다. 

 

 

 

 


 

 

 

 

♣ 참고 및 인용