Eager Initialization
class EagerInitialization {
// private static 로 선언.
private static EagerInitialization instance = new EagerInitialization();
// 생성자
private EagerInitialization() {
System.out.println("call EagerInitialization constructor.");
}
// 조회 method
public static EagerInitialization getInstance() {
return instance;
}
public void print() {
System.out.println("It's print() method in EagerInitialization instance.");
System.out.println("instance hashCode > " + instance.hashCode());
}
}
- private 접근제어자 때문에 이 인스턴스를 접근해서 사용하려면 getInstance() 메소드를 사용해야한다.
- 다른 클래스들이 이 클래스를 많이 사용하면 아마도 클래스가 로드되는 시점에서 인스턴스를 생성시키는데 무리가 있을 수 있다.
- 이 클래스의 인스턴스를 만들어 주는 과정에서 어떠한 예외 처리도 할 수 없다.
Static Block Initialization
class StaticBlockInitialization {
private static StaticBlockInitialization instance;
private StaticBlockInitialization() {
}
static {
try {
System.out.println("instance create..");
instance = new StaticBlockInitialization();
} catch (Exception e) {
throw new RuntimeException("Exception creating StaticBlockInitialization instance.");
}
}
public static StaticBlockInitialization getInstance() {
return instance;
}
public void print() {
System.out.println("It's print() method in StaticBlockInitialization instance.");
System.out.println("instance hashCode > " + instance.hashCode());
}
}
- static 초기화블럭을 이용하면 클래스가 로딩 될 때 최초 한번 실행하게 된다.
- 초기화블럭을 이용하면 logic을 담을 수 있기 때문에 복잡한 초기변수 셋팅이나 위와 같이 에러처리를 위한 구문을 담을 수 있다.
- 인스턴스가 사용되는 시점에 생성되는 것은 아니다.
lazy initialization
class LazyInitialization {
private static LazyInitialization instance;
private LazyInitialization() {
}
public static LazyInitialization getInstance() {
if (instance == null)
instance = new LazyInitialization();
return instance;
}
public void print() {
System.out.println("It's print() method in LazyInitialization instance.");
System.out.println("instance hashCode > " + instance.hashCode());
}
}
- new LazyInitialization(); 는 getInstance() method 안에서 사용되었다. if문을 이용해 instance가 null 인 경우에만 new를 사용해 객체를 생성한다.
- 최초 사용시점에만 인스턴스화 시키기 때문에 프로그램이 메모리에 적재되는 시점에 부담이 많이 줄게된다.
- 만약 프로그램이 muilti thread 방식이라면 위와 같은 singleton pattern은 안전하지 않다.
Thread Safe Initalization
class ThreadSafeInitalization {
private static ThreadSafeInitalization instance;
private ThreadSafeInitalization() {
}
public static synchronized ThreadSafeInitalization getInstance() {
if (instance == null)
instance = new ThreadSafeInitalization();
return instance;
}
public void print() {
System.out.println("It's print() method in ThreadSafeInitalization instance.");
System.out.println("instance hashCode > " + instance.hashCode());
}
}
- muilit thread문제를 해결하기 위해 synchronized(동기화)를 사용하여 singleton pattern을 구현했다.
- 수 많은 thread 들이 getInstance() method 를 호출하게 되면 높은 cost 비용으로 인해 프로그램 전반에 성능저하가 일어난다.
Initialization On Demand Holder Idiom
class InitializationOnDemandHolderIdiom {
private InitializationOnDemandHolderIdiom() {
}
private static class Singleton {
private static final InitializationOnDemandHolderIdiom instance = new InitializationOnDemandHolderIdiom();
}
public static InitializationOnDemandHolderIdiom getInstance() {
System.out.println("create instance");
return Singleton.instance;
}
public void print() {
System.out.println("InitializationOnDemandHolderIdiom");
}
}
- jvm 의 class loader의 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 thread 간의 동기화 문제를 해결한다.
- initialization on demand holder idiom 역시 lazy initialization이 가능하며 모든 java 버젼과, jvm에서 사용이 가능하다.
- java 에서 singleton 을 생성시킨다고 하면 거의 위의 방법을 사용한다고 보면 된다.
Enum Initialization
enum EnumInitialization {
INSTANCE;
static String test = "";
public static EnumInitialization getInstance() {
test = "test";
return INSTANCE;
}
}
- INSTANCE 가 생성될 때, multi thread 로 부터 안전하다. (추가된 methed 들은 safed 하지 않을 수도 있다.)
- 단 한번의 인스턴스 생성을 보장한다.
- 사용이 간편하다.
- enum value는 자바 프로그램 전역에서 접근이 가능하다.
Using Reflection To Destroy Singleton
class UsingReflectionToDestroySingleton {
public static void main(String[] args) {
EagerInitialization instance = EagerInitialization.getInstance();
EagerInitialization instance2 = null;
try {
Constructor[] constructors = EagerInitialization.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
constructor.setAccessible(true);
instance2 = (EagerInitialization) constructor.newInstance();
}
} catch (Exception e) {
}
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
}
- 위의 코드를 실행해보면 아래 System.out.println();의 두 라인에서 찍히는 hachCode()값이 다른 것을 확인 할 수 있다.
- java의 reflection은 매우 강력하다. 설령 class 의 생성자가 private 일지라도 강제로 가져와서 새로운 인스턴스 생성이 가능하다.
- 결국 singleton pattern을 깨뜨리는 것이다. 이 외에도 reflection을 여러곳에서 사용할 수 있으니 알아두는 것이 좋다.
참고
- https://blog.seotory.com/post/2016/03/java-singleton-pattern