소프트웨어 패턴은 목적에 따라 생성 패턴, 구조 패턴, 행위 패턴으로 나뉜다.
오늘은 생성 패턴의 싱글톤 패턴에 대해 공부해보도록 하겠다.
1. Singleton Pattern 사용 이유?
싱글톤 패턴(Singleton Pattern)
- 프로그램내에서 하나의 인스턴스만 만들어서, 언제 어디서든 그 인스턴스에 접근할 수 있도록 하기 위한 패턴
- 커넥션 풀이나(DBCP) 환경 설정 클래스 같은, 하나만 사용되어야 하는 객체를 만들때 유용하다.
- 생성자를 private로 선언하여 상속이 불가능함을 지정하기도 한다.
2. Singleton Pattern 예제
Lazy initialization
public class Singletone1 { private static Singletone1 singletone; public static Singletone1 getInstance() { if ( null == singletone ) { singletone = new Singletone1(); return singletone; } return singletone; } }
해당 laze initialization은 필요할때 인스턴스를 생성시는 것이 핵심이다.
Singletone1 객체는 getInstance method 내, if문으로 null인 경우에만 생성하였다.
이는 최초 사용할때만 인스턴스화 시키기때문에 객체가 heap으로 올라갈때의 시간과 비용을
줄일 수 있다고 한다. 하지만 문제는 프로그램이 multi thread 환경일때, 위와 같은 방법은 잠재적인 문제를 가지고 있다.
그 이유는 getInstance() method를 호출하면 인스턴스가 두번 생성될 위험이 있기 때문이다.
그렇다면 위와 같은 잠재적인 위험에서 안전한 코드를 작성하려면 어떻게 해야될까?
첫번째로는 getInstance() method를 동기화(synchronized) 시키는 것이다.
Synchronized(해결1)
public class Singletone2 { private static Singletone2 singletone; public synchronized static Singletone2 getInstance() { if ( null == singletone ) { singletone = new Singletone2(); return singletone; } return singletone; } }
이로써 여러개의 thread들이 동시에 접근해서 인스턴스를 생성하는 위험은 피했지만,
여러 thread들이 getInstance() method를 호출하게 되면 높은 비용(cost)로 인해 프로그램 전반적으로
성능 저하가 발생한다.
LazyHolder(해결2)
public class Singletone3 { public Singletone3() {} private static class LazyHolder { static final Singletone3 SINGLETONE3 = new Singletone3(); } public static Singletone3 getInstance() { return LazyHolder.SINGLETONE3; } }
위 코드는 LazyHolder를 통해 싱글톤의 동시성 문제를 해결하는 방법이다.
이는 자바 버전에 상관없으며, 상기 synchronized 키워드도 사용하지 않는다
여기선 Singletone3 class는 클래스 로드 시점에서 초기화 되지만, static class로 정의된 LazyHolder는 초기화 되지않고
static method인 getInstance()가 호출될때에만 실행된다. 이는 JVM에서 클래스를 로딩하고 초기화할때 원자성이
보장되기 때문에 thread safety한 방법이다.