본문 바로가기

Java

Singleton Pattern (싱글턴 패턴)

* Singleton Pattern (싱글턴 패턴)

  • 싱글턴 패턴은 해당 클래스의 인스턴스가 오직 하나만 만들어진다.
  • 어디서나 그 인스턴스에 접근할 수 있도록 한다.
  • 클래스에서 자신의 단 하나뿐인 인스턴스를 관리하도록 만들면 된다.


* 용도

  • 스레드 풀 , 캐시 , 대화상자 , 사용자 설정 , 커넥션 풀 , 디바이스 드라이버 등 객체가 전체 프로그램에서 오직 하나만 생성되어야 하는 경우


* 전역 변수로 static으로 선언해서 사용하면 되지 않을까?

  • 만약 전역 변수로 객체를 생성한다면 어플리케이션이 실행 될 때 객체가 생성 될 것이다. 그러나 그 객체가 자원을 많이 차지 한다면 사용도 하기 전에, 괜한 자원만 차지하게 된다.


* 고전적인 싱글턴 패턴 구현법

아래 방식의 코드는 다중 스레드를 사용할 때 각 스레드가 getInstance 메소드를 동시에 호출하면(거의 비슷하게) 두 개의 객체가 생겨버릴지도 모른다. 지금은 간단하지만 나중에 복잡한 인스턴스가 두 개 이상 존재할 경우 어떤 결과를 초래하게 될 지 알 수 없다.

public class Singleton {
 
    // private으로 Sinleton클래스의 유일한 인스턴스를 
    // 저장하기 위한 정적 변수를 선언
    private static Singleton uniqueInstance;
 
    // 생성자를 private로 선언했기 때문에 Singleton에서만 
    // 클래스를 만들 수 있다.
    private Singleton() {
    }
 
    // 클래스의 인스턴스를 만들어서 리턴해 준다.
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
 
}


* 멀티스레딩 문제 해결 방법

getInstance() 를 동기화시키기만 하면 멀티스레딩과 관련된 문제가 간단하게 해결된다. 그러나 동기화를 하게 되면 불필요한 오버헤드가 생기기 때문에 속도 문제가 발생할 수 있다. (대부분 그렇다.)

public class Singleton {
    private static Singleton uniqueInstance;
 
    // 기타 인스턴스 변수
    private Singleton() {
    }
 
    // synchronized 키워드만 추가하면 두 스레드가 
    // 이 메소드를 동시에 실행시키는 일은 일어나지 않게 된다.
    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    // 기타 메소드
}


* 더 효율적인 방법

1. getInstance()의 속도가 그리 중요하지 않다면 그냥 내버려 둔다.

  • 메소드를 동기화하면 성능이 100배 정도 저하된다는 것을 기억하자.
  • 만약 getInstance() 가 어플리케이션에서 병목으로 작용한다면 다른 방법을 생각해봐야 한다.


2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.

이런 접근법을 사용하면 클래스가 로딩될 때 JVM에서 Singleton의 유일한 인스턴스를 생성해 준다.

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();
 
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        return uniqueInstance;
    }
}


3. DCL(Double-Checking Locking)을 써서 getInstance()에서 동기화되는 부분을 줄인다.

DCL(Double-Checking Locking)을 사용하면, 일단 인스턴스가 생성되어 있는지 확인한 다음, 생성되어 있지 않았을 때만 동기화를 할 수 있다. volatile 키워드를 사용하여 멀티스레딩을 쓰더라도 uniqueInstance 변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 할 수 있다. DCL은 자바 1.4 이하 버전에서 사용할 수 없다.

public class Singleton {
    private volatile static Singleton uniqueInstance;
 
    private Singleton() {   }
 
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            // 이렇게 하면 처음에만 동기화 된다
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}