Singleton
인스턴스가 사용될 때, 동일한 인스턴스를 사용하게끔 하는 것이 기본 전략이다. 동일한 인스턴스를 사용한다는 말은 identity가 동일한 인스턴스를 재 사용하겠다는 의미이다. 자바에서는 equals() 와 identity() 의 구분이 매우 중요하다. 해당 내용은 여기 글을 참고하는 것이 좋다.
동등한 객체를 하나 만드는 것이 아닌 동일한 객체를 사용하는 것인데, 싱글톤은 하나만 사용되어야 하는 객체를 만들 때 매우 유용하다. 또한 core java(java.lang.core, java.awt.Desktop 등등) 에서도 singleton pattern이 사용된다.
코드의 전문은 본인 깃헙에 올려두었다.
우선 기본적인 싱글톤 패턴을 살펴보자.
참고로 해당 예제는 참고링크에서 그대로 따왔음을 밝힌다.
Eager Initialization
가장 기본적인 싱글톤 패턴이다. 전역 변수로 instance 를 만드는데, private static 을 통해서 접근이 가능하다. static 이 붙었기 때문에 클래스변수는 인스턴스화에 상관없이 사용이 가능하다. 하지만 앞선 private 접근지정자 때문에 EagerInitialization.instance로의 접근은 불가능하다.
이런 클래스 변수에 해당 클래스의 생성자 또한 private 로 설정해두면 해당 클래스는 new 키워드를 사용할 수 없게 되므로 해당 클래스를 외부 클래스에서 인스턴스 생성 방법인 EagerInitialization instance = new EagerInitialization() 으로 사용하지 못한다.
따라서 외부 클래스가 해당 클래스의 인스턴스를 가지려면 public static 으로 선언된 getInstance() 메소드를 사용할 수 밖에 없다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class EagerInitialization { // private static 으로 전역변수 초기화 // EagerInitialization.instance 로 접근 불가 private static EagerInitialization instance = new EagerInitialization(); // 인스턴스가 private 으로 선언. 생성자도 private private EagerInitialization(){ System.out.println("Call EagerInitialization Constructor"); } // 외부 클래스에서 EagerInitialization 클래스의 인스턴스를 가질 수 있는 유일한 방법이다. 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()); } } | cs |
만약 수 많은 클래스들이 위의 싱글톤 패턴을 사용하고 getInstance() 메소드를 통해서 해당 클래스의 인스턴스를 획득한다고 가정하자. new EagerInitialization으로 인해 클래스가 Load 되는 시점에 인스턴스를 생성시키는데 해당 프로그램에 부담이 될 수 있다. 또한 이 소스는 EagerInitialization 클래스가 인스턴스화 되는 시점에서 어떠한 에러처리도 할 수 없다고 한다.
Static Block Initialization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class StaticBlockInitialization { private static StaticBlockInitialization instance; // 생성자 private StaticBlockInitialization(){} // 클래스가 로딩될 때 최초 1번 실행 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 StaticBlockInitalization instance."); System.out.println("instance hashCode > " + instance.hashCode()); } } | cs |
static 초기화 블럭을 이용, 클래스가 로딩될 때 최초 1회만 실행한다. 특히 초기화 블럭을 이용하면 로직을 담을 수 있기 때문에 복잡한 초기변수 셋팅이나 코드와 같이 에러 처리 구문 try - catch 문을 넣을 수 있다. 그러나 앞선 문제와 동일하게 인스턴스가 사용되는 시점에 생성되는 것은 아니다.
Lazy Initialization
Lazy Initialization pattern 은 필요할 때 인스턴스를 생성하는 것이 핵심이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public 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()); } } | cs |
new LazyInitialization(); 의 선언된 위치를 살펴보면 getInstance() method 안에 사용되었다. if 문을 이용해 instance 가 null인 경우에만 new를 사용, 해당 인스턴스를 생성하였다. 최초 사용시점에만 인스턴스화 시키기 때문에 프로그램이 메모리에 적재되는 시점에 부담이 줄게된다.
하지만 멀티스레드 환경에서 위의 싱글톤 패턴을 이용하게 되면 동일한 시점에 getInstance() method를 사용하기 때문에 인스턴스가 두 번 생길 우려가 있다.
Thread Safe Initialization
LazyInitialization의 멀티스레드에서의 중복 인스턴스 생성을 방지하기 위해서 동기화 기법을 이용한다. 여러 스레드들이 동시에 인스턴스를 생성시키는 위험을 없애기 위함이다. 하지만 수 많은 스레드들이 getInstance() method를 호출하게 되면 높은 cost 비용으로 프로그램 전반에 걸쳐 성능 저하가 나타난다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class ThreadSafeInitialization { private static ThreadSafeInitialization instance; private ThreadSafeInitialization(){} // 멀티스레드 문제를 해결하기 위해 동기화 처리 // 여기서 많은 스레드들이 getInstance() 를 호출하게 되면 높은 cost 발생 ㅡ> 성능저하 public static synchronized ThreadSafeInitialization getInstance(){ if(instance == null) instance = new ThreadSafeInitialization(); return instance; } public void print () { System.out.println("It's print() method in ThreadSafeInitalization instance."); System.out.println("instance hashCode > " + instance.hashCode()); } } | cs |
Initialization on demand Holder idiom
참고링크에 따르면 미국 어느 대학의 컴퓨터 과학 연구원인 Biil pugh가 기존의 java singleton pattern이 가지고 있는 문제들을 해결하기 위해서 새로운 singleton pattern을 제시하였다. Initialization on demand holder idiom 기법이다. 이것은 JVM의 Class Loader 의 매커니즘과 클래스 로드시점을 이용하여 내부 class를 생성시킴으로 thread 간의 동기화 문제를 해결한다.
현재 자바에서 싱글톤을 생성시킨다고 하면 거의 아래의 방법을 사용한다고 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * * * JVM 의 class Loader의 매커니즘과 class의 load 시점을 이용하여 * 내부 클래스를 생성시킴으로 thread 간의 동기화 문제를 해결한다. * * **/ public 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; } } | cs |
Enum Initialization
Joshua Bloch가 작성한 effective Java 책에서 enum singleton 방법이 소개되었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public enum EnumInitialization { INSTANCE; static String test = ""; public static EnumInitialization getInstance(){ test = "test"; return INSTANCE; } /** * * enum 이 sigleton pattern 으로 사용될 수 있는 이유는 아래와 같다. * (1) INSTANCE 가 생성될 때, multi thread 로 부터 안전하다. * (추가된 method 들은 safety 하지 않을 수 있다.) * (2) 단 한번의 인스턴스 생성을 보장한다. * (3) 사용이 간편하다. * (4) enum value 는 자바 프로그램 전역에서 접근이 가능하다. * * **/ } | cs |
Using reflection to destroy singleton
자바의 리플렉션 API를 이용해서 싱글톤 패턴을 깨뜨려 보는 방식이다. 누군가 작성한 코드를 원본 수정 없이 작업해야 할 때 이용될 수 있다고 한다.
참고링크를 보면서 그대로 작성하였지만 해당 글쓴이의 코드에서 문법오류가 발생되서 약간 수정했다. 마지막에 instance1 과 instance2에 대한 해쉬코드를 출력하는 구문이 있는데, 서로 값이 다른 것을 확인가능하다. 만약 클래스의 생성자가 private 이라고 하더라도 강제로 새로운 인스턴스의 생성이 가능하다. 결국 싱글톤 패턴을 깨뜨리는 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import java.lang.reflect.Constructor; public class UsingReflectionToDestroySingleton { public static void main(String[]args){ EagerInitialization instance1 = EagerInitialization.getInstance(); EagerInitialization instance2 = null; try{ Constructor<EagerInitialization> constructor = EagerInitialization.class.getDeclaredConstructor(); constructor.setAccessible(true); instance2 = (EagerInitialization)constructor.newInstance(); } catch(Exception e){ // catch 문 } System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); } } | cs |
'프로그래밍 언어' 카테고리의 다른 글
20191213 DDD 에서 사용하는 표현 및 용어 정리. (0) | 2019.12.13 |
---|---|
20190427 스트래티지 패턴 (Strategy Pattern) (0) | 2019.04.27 |
20180129 Loose Coupling & High Cohesion 2 (0) | 2018.01.29 |
20180128 Loose Coupling & High Cohesion (0) | 2018.01.28 |
20180125 IS-A & HAS-A relationship (2020-10-04) (0) | 2018.01.25 |