=======================================================================
Singleton Pattern - 디자인 패턴
참고서적 : Head First Design Pattern
소스코드 다운로드 : http://www.wickedlysmart.com/headfirstdesignpatterns/code.html
패턴 정의 #6 - Singleton Pattern
싱글턴 패턴 (Singleton Pattern)은 해당 클래스의 인스턴스가 하나만 만들어지고,
어디서든지 인스턴스에 접근할 수 있도록 하기 위한 패턴입니다.
가장 널리 사용하는 디자인 패턴이 아닌가 합니다.
굳이 디자인 패턴을 모르더라도 자바 프로그래밍에서 자주 사용하기도 하죠~~
싱글턴 패턴은 클래스에서 자신의 단 하나뿐인 인스턴스를 관리하도록 만들면 됩니다.
그리고 다른 어떤 클래스에서도 자신의 인스턴스를 추가로 만들지 못하도록 해야 합니다.
인스턴스가 필요하면 반드시 클래스 자신을 거치도록 해야 되겠죠..
- uniqueInstance 클래스 변수에 싱글턴의 유일무이한 인스턴스가 저장됩니다.
- getInstance() 메소드는 정적 메소드, 즉 클래스 메소드입니다.
그냥 Singleton.getInstance() 라는 코드만 사용하면 언제 어디서든 이 메소드를 호출할 수 있습니다.
전역 변수에 접근하는 것만큼이나 쉬우면서도 게으른 인스턴스 생성을 활용할 수 있다는 장점을 제공합니다.
- 싱글턴 패턴을 구현한다고 해서 여기 있는 Singleton 클래스처럼 간단해야 하는 것은 아닙니다.
그냥 일반적인 클래스를 만들 때와 마찬가지로 다양한 데이터와 메소드를 사용할 수 있습니다.
싱글턴 패턴을 사용할 경우, 한가지 고려해야 할 사항이 바로 멀티스레딩 관련 문제입니다.
두 개의 쓰레드가 동시에 생성할 경우, 인스턴스가 두 개 생길 수도 있다는 것이죠..
그래서 이에 대한 해결책으로 다음과 같은 세가지 방법을 사용합니다.
참고하시기 바랍니다.
1. synchronized 키워드를 사용한다.
synchronized로 동기화 하게 되면 비용 측면에서의 문제가 발생할 수 있으므로
getInstance()의 속도가 그리 중요하지 않을 경우, 사용해야 한다고 합니다.
- public class Singleton {
- private static Singleton uniqueInstance;
- // other useful instance variables here
- private Singleton() {}
- public static synchronized Singleton getInstance() {
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- return uniqueInstance;
- }
- // other useful methods here
- }
2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.
실행중에 만들지 않고 처음부터 Singleton 인스턴스를 만드는 방법입니다.
- public class Singleton {
- private static Singleton uniqueInstance = new Singleton();
- private Singleton() {}
- public static Singleton getInstance() {
- return uniqueInstance;
- }
- }
클래스가 로딩 될 때, JVM에서 Singleton의 유일한 인스턴스를 생성한다고 합니다.
그러므로 JVM에서 유일한 인스턴스를 생성하기 전에는 어떤 쓰레드도 uniqueInstance 변수에 접근할 수 없습니다.
3. DCL(Double Checking Locking)을 써서 getInstance()에서 동기화 되는 부분에 대한 체크를 줄입니다.
DCL은 Java 5 이전 버전에는 사용할 수 없습니다. 즉, 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;
- }
- }
volatile 키워드를 사용하면 멀티쓰레딩을 쓰더라도 uniqueInstance 변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 진행되도록 할 수 있습니다.
참조 - http://www.word.pe.kr/bbs/view.php?id=lecture&no=29
=============================================================================================
I. Singleton 이란?
아마 GOF의 32가지 패턴 중 가장 쉬우면서도 가장 자주 쓰이게 되며, 가장 문제가 될 소지를 가지는 패턴을 말하면 Singleton을 말할 수 있지 않을까 합니다.
먼저 Singleton 패턴의 용도는 하나의 프로그램 내에서 하나의 인스턴스만을 생성해야만 하는 상황. 예를 들어 환경설정을 관리하는 클래스나
Connection Pool, Thread Pool과 같이 풀(Pool) 형태로 관리되는 클래스의 경우 프로그램 내에서 단 하나의 인스턴스로 관리되는 것이 일반적이며, 이 때 Singleton 패턴을 적용하는 것이 일반적인 경우라고 볼수 있겠습니다. 그럼 세부적인 구현 형태를 살펴 보도록 하겠습니다.
II. Singleton 구현
Singleton 패턴의 가장 일반적인 형태는 다음과 같습니다.
<일반적인 Singleton class diagram>
중요하게 보아야 할 곳은 생성자가 private으로 선언되어 있다는 것이죠. 이유는 간단하게도 클래스 외부에서 이 클래스를 직접 new 키워드로 생성하지 못하게 막겠다는 의미가 되겠습니다. Singleton 패턴은 설명보다는 코드로 보는 것이 훨씬 간결하므로 다음 코드를 보도록 하겠습니다.
Singleton 패턴
class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
}
위 코드에서 파란색으로 표기된 선언부에 집중해야 합니다. Singleton 클래스의 인스턴스를 외부에서 사용하기 위해서는 항상 getInstance 메소드를 호출해야만 하도록 강제된 것을 확인하실 수 있습니다. 위와 같은 경우 getInstance가 호출될 때 객체가 생성되는데 최초 로딩 시에 객체를 생성해 두는 것이 효율적인 경우에는 다음과 같이 멤버 변수를 선언하는 곳에서 직접 생성하는 방법을 이용할 수 있습니다.
- ClassLoader에 의해 미리 인스턴스를 생성하는 방법
- class Singleton {
- private static Singleton instance = new Singleton();
- private Singleton(){}
- public static Singleton getInstance(){
- if (instance == null)
- instance = new Singleton();
- return instance;
- }
- }
위의 코드에서 붉은색으로 표기된 멤버 변수 선언부를 보면 new 키워드에 의해 static으로 선언된 멤버 변수의 인스턴스를 미리 생성하는 것을 볼 수 있습니다.
다중 쓰레드 프로그래밍에 필요한 것이 아니라면 위의 코드만으로도 완성된 코드라고 볼수 있겠습니다. 미리 인스턴스를 생성할 필요가 없는 경우에는 이전의 방법을 이용하는데 그런한 방식을 lazy(게으른) 방식이라고 이야기 합니다.
다중 쓰레드 프로그래밍에서는 동기화를 이용한 적절한 수정이 필요한데 많은 커뮤니티에서 그에 따른 예제 코드가 잘 나와있습니다.
(Singleton의 다양한 형태 : http://www.anfamily.net/mcsong/70)
위의 블로그에 방문하시면 싱글톤 패턴의 다양한 종류와 그에 따른 장단점이 잘 분류되어 있습니다.
다만 좀더 자세히 내용을 들여다 보면 가장 간단한 방법이 오히려 득이 되는 경우를 찾을 수 있을 것 입니다. 다음 코드를 봐주세요.
Instance의 생성은 단 한번만
class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
위의 코드의 녹색으로 표시된 부분을 봐주세요. Simple is best 라는 말 처럼 위의 코드는 Thread-safe하게 잘 만들어진 코드라고 할 수 있겠습니다.
이 클래스는 전체 라이프 타임 중에서 단 한번만 객체를 생성하고 다시는 생성하지 않습니다. 여러개의 쓰레드가 접근을 한다고 하더라도 인스턴스가 두개 이상 생성되거나 Null값을 리턴 받을 염려도 생성자가 두번 호출 되는 경우도 없죠.
III. 결론
하나의 패턴은 다양한 형태로 표현되는 경우가 많습니다. 어쩔 때는 경계가 모호한 형태로 패턴이 구현되는 경우도 있지요.
이번 싱글톤 패턴은 목적과 형태가 분명한 패턴으로 항상 머리에 넣어두고 사용해야할 중요한 패턴이라고 말할 수 있습니다.
참고문헌
- mcsong's languid afternoon : http://www.anfamily.net/mcsong/70
- Data & Object Factory : http://www.dofactory.com/Patterns/PatternSingleton.aspx
출처 - http://sakula99.egloos.com/2971297
===================================================================================
싱글톤(Singleton) Pattern 이란?
객체지향형언어(OOP)에 대해 조금이라도 파고든 사람이라면 싱글톤 패턴이라는
말을 들어봤을 것이다. 못들어봤다면 이제부터 들었다고 해라. 싱글톤이란 생성하고자
하는 인스턴스의 수를 오직 하나로 제한하는 디자인 패턴이다.
그렇다면 왜 싱글톤 패턴을 사용해야하는 것일까? 라는 질문에 대게 답하는 것이
여러개의 인스턴스를 생성하게 되면 가끔 프로그래머도 당혹스럽게 되는 서로의
인스턴스에 간섭을 하는 경우가 있다. 정말 재수 없는 일이 아닐 수가 없다.
public class Singleton
{
private static Singleton singleton = new Singleton();
protected Singleton()
{
System.out.println("Maked Singleton");
}
public static Singleton getInstance()
{
return singleton;
}
}
싱글톤의 기본형이다. singleton 멤버변수는 static 이어야한다는 것과 Singleton 클래스의
생성자는 private / protected 이어야한다는 것을 꼭 유념해야한다. private 일 경우는 결코
new 를 이용하여 인스턴스의 중복 생성을 방지하는 셈이기도 하나 상속이 되지 않는다는
단점이 있어 protected로 대게 선언한다.
뭐~ 싱글톤 패턴이 만들어졌나 아닌가 확인할 것이라면 Test 클래스를 만들어보자.
public class Test
{
public static void main(String [] args)
{
System.out.println("Singleton pattern");
Singleton sg1=Singleton.getInstance();
Singleton sg2=Singleton.getInstance();
if( sg1 == sg2 )
{
System.out.println("Equal");
}
else
{
System.out.println("Not Equal");
}
}
}
여기서 보면 Singleton 의 인스턴스를 생성하기 위해 getInstance() 메소드를 이용한다.
왜 그럴까? Singleton 클래스의 private static Singleton singleton = new Singleton();
부분을 유심히 바라보기 바란다. 이 singleton은 static으로 선언된다. 즉 하나의 인스턴스
singleton 만 생성하는 셈이다. 아마 결과도 Equal로 출력될 것이다.
출처 - http://coolx.net/cboard/read.jsp?db=develop&mode=read&num=261
singleton 이란?
프로그래밍 세계에 OOP 의 개념이 생기면서 객체 자체에 대한 많은 연구와 패턴(pattern)들이 생겨났다. singleton pattern은 인스턴스가 사용될 때에 똑같은 인스턴스를 만들어 내는 것이 아니라, 동일 인스턴스를 사용하게끔 하는 것이 기본 전략이다. 프로그램상에서 동일한 커넥션 객체를 만든다던지, 하나만 사용되야하는 객체를 만들때 매우 유용하다. singleton pattern은 4대 디자인 패턴에 들어갈 정도로 흔히 쓰이는 패턴이다. 물론 core java(java.lang.Runtime, java.awt.Desktop 등등)에서도 singleton pattern이 사용된다.
Eager initialization
아래가 가장 기본적인 singleton pattern이다. 전역 변수로 instance를 만드는데 private static
을 이용한다. static
이 붙은 클래스변수는 인스턴스화에 상관없이 사용이 가능하게 된다. 하지만 앞의 private
접근제어자로 인해EagerInitialization.instance
로의 접근은 불가능하다. 이런 상태에서 생성자를 private
로 명시한다. 생성자를private
로 붙이게되면, new
키워드를 사용할 수 없게된다. 즉 다른 클래스에서 EagerInitialization instance = new EagerInitialization();
이런 방법을 통한 인스턴스 생성은 불가능해진다. 결국 외부 클래스가 EagerInitialization 클래스의 인스턴스를 가질 수 있는 방법은 11번째 라인에 있는 getInstance()
method를 사용하는 수 밖에 없다.
public 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());
}
}
위의 단순한 singleton pattern은 리소스가 작은 프로그램일때엔 고도화 대상이 아니다. 하지만 프로그램의 크기가 커져서 수 많은 클래스에서 위와 같은 singleton pattern을 사용한다고 가정해보자. 3번째 라인의 new EagerInitialization();
으로 인해 클래스가 load 되는 시점에 인스턴스를 생성시키는데 이마저도 부담스러울 수가 있다. 또한 이 소스는 EagerInitialization 클래스가 인스턴스화 되는 시점에 어떠한 에러처리도 할 수가 없다.
static block initialization
public class StaticBlockInitalization {
private static StaticBlockInitalization instance;
private StaticBlockInitalization () {}
static {
try {
System.out.println("instance create..");
instance = new StaticBlockInitalization();
} catch (Exception e) {
throw new RuntimeException("Exception creating StaticBlockInitalization instance.");
}
}
public static StaticBlockInitalization getInstance () {
return instance;
}
public void print () {
System.out.println("It's print() method in StaticBlockInitalization instance.");
System.out.println("instance hashCode > " + instance.hashCode());
}
}
static
초기화블럭을 이용하면 클래스가 로딩 될 때 최초 한번 실행하게 된다. 특히나 초기화블럭을 이용하면 logic을 담을 수 있기 때문에 복잡한 초기변수 셋팅이나 위와 같이 에러처리를 위한 구문을 담을 수 있다. 첫 번째 패턴보다 좋아보이지만 인스턴스가 사용되는 시점에 생성되는 것은 아니다.
lazy initialization
이제 클래스 인스턴스가 사용되는 시점에 인스턴스를 만드는 singleton pattern을 배워보도록 하자. 아래 소스의 lazy initialization pattern은 필요할때 인스턴스를 생성시키는 것이 핵심이다.
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());
}
}
new LazyInitialization();
가 어디에 선언되었는지 주목해보자. getInstance()
method 안에서 사용되었다. if문을 이용해 instance가 null 인 경우에만 new
를 사용해 객체를 생성하였다. 최초 사용시점에만 인스턴스화 시키기 때문에 프로그램이 메모리에 적재되는 시점에 부담이 많이 줄게된다. 하지만 여전히 문제는 남아있다. 만약 프로그램이 muilti thread 방식이라면 위와 같은 singleton pattern은 안전하지 않다. 동일 시점에 getInstance()
method를 호출하면 인스턴스가 두번 생길 위험이 있다.
thread safe initalization
위에서 문제가 되었던 muilit thread문제를 해결하기 위해 synchronized(동기화)를 사용하여 singleton pattern을 구현한다. 여러 thread들이 동시에 접근해서 인스턴스를 생성시키는 위험은 없어졌다. 하지만 수 많은 thread 들이 getInstance()
method 를 호출하게 되면 높은 cost 비용으로 인해 프로그램 전반에 성능저하가 일어난다.
public 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());
}
}
initialization on demand holder idiom
미국 메릴랜드 대학의 컴퓨터 과학 연구원인 Bill pugh 가 기존의 java singleton pattern이 가지고 있는 문제들을 해결 하기 위해 새로운 singleton pattern을 제시하였다. Initialization on demand holder idiom기법이다. 이것은 jvm 의 class loader의 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 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;
}
}
initialization on demand holder idiom 역시 lazy initialization이 가능하며 모든 java 버젼과, jvm에서 사용이 가능하다. 현재 java 에서 singleton 을 생성시킨다고 하면 거의 위의 방법을 사용한다고 보면 된다.
enum initialization
Joshua Bloch가 작성한 effective java 책에서 enum singleton 방법이 소개 되었다.
public enum EnumInitialization {
INSTANCE;
static String test = "";
public static EnumInitialization getInstance() {
test = "test";
return INSTANCE;
}
}
enum 이 singleton pattern 으로 사용될 수 있는 이유는 아래와 같다.
- INSTANCE 가 생성될 때, multi thread 로 부터 안전하다. (추가된 methed 들은 safed 하지 않을 수도 있다.)
- 단 한번의 인스턴스 생성을 보장한다.
- 사용이 간편하다.
- enum value는 자바 프로그램 전역에서 접근이 가능하다.
using reflection to destroy singleton
위에서 여러 방법으로 singleton을 만들어 보았으니 이번엔 java의 reflection을 이용하여 singleton을 깨뜨려 보는법도 배워보자. 누군가 작성한 코드를 원본 수정없이 작업해야 할때 이용될 수 있을 것이다.
public 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
'Development > Java' 카테고리의 다른 글
오라클 vs 아파치재단, 자바 놓고 법적 분쟁 돌입? (0) | 2010.12.13 |
---|---|
아파치재단, 자바 커뮤니티 탈퇴…오라클 자바 통제에 반기 (0) | 2010.12.13 |
jconsole 사용하기 (0) | 2010.12.03 |
리눅스에 classpath 설정 (0) | 2010.11.17 |
JVM 메모리 (0) | 2010.11.15 |