싱글턴 패턴은 클래스에서 자신의 단 하나뿐인 인스턴스를 관리하도록 만들면 됩니다.
그리고 다른 어떤 클래스에서도 자신의 인스턴스를 추가로 만들지 못하도록 해야 합니다.
인스턴스가 필요하면 반드시 클래스 자신을 거치도록 해야 되겠죠..
1. synchronized 키워드를 사용한다.
synchronized로 동기화 하게 되면 비용 측면에서의 문제가 발생할 수 있으므로 getInstance()의 속도가 그리 중요하지 않을 경우, 사용해야 한다고 합니다
2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.
실행중에 만들지 않고 처음부터 Singleton 인스턴스를 만드는 방법입니다.
클래스가 로딩 될 때, JVM에서 Singleton의 유일한 인스턴스를 생성한다고 합니다.
그러므로 JVM에서 유일한 인스턴스를 생성하기 전에는 어떤 쓰레드도 uniqueInstance 변수에 접근할 수 없습니다.
3. DCL(Double Checking Locking)을 써서 getInstance()에서 동기화 되는 부분에 대한 체크를 줄입니다.
DCL은 Java 5 이전 버전에는 사용할 수 없습니다. 즉, 1.4 버전에서는 지원이 안된다는 것이죠~
어쨌든 동기화를 체크하는 부분이 처음에만 이루어지므로 성능에 큰 영향을 주지 않게 됩니다.
volatile 키워드를 사용하면 멀티쓰레딩을 쓰더라도 uniqueInstance 변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 진행되도록 할 수 있습니다.
--------------------------------------------------------------------------------------------------------
유일무이한 객체
- 싱글턴패턴은 인스턴스가 하나 뿐인 특별한 객체를 만들 수 있게 해주는 패턴이다.
- 어떤 용도로 쓰는 건가?
- 스레드 풀이라던가, 캐시, 대화상자, 사용자설정, 디바이스드라이버 등등 객체가 전체프로그램에서 오직 하나만 생성되어야 하는 경우에 사용한다.
- 그럼 전역변수로 static 으로 선언해서 사용하면 되지 않느냐?
전역 변수로 객체를 생성하면 어플리케이션이 실행 될 때 객체가 생성될 것이다.(P208 맨밑줄)
그 객체가 자원을 많이 차지 한다면 사용도 되기전에, 괜히 자원만 차지한다. 사용하지 않는다면 아무 쓸 데 없는 객체가 되겠지.
고전적인 싱글턴 패턴 구현법
- 조심하세요.. 이 코드에 문제가 있다는 것을 알게 될 것입니다
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
초콜릿 공장
- 만약 애플리케이션에서 ChocolateBoiler 인스턴스가 두 개 이상 만들어지게 되면 어떤 문제가 생길까?
public class ChocolateBoiler {
private boolean empty;
private boolean boiled;
private ChocolateBoiler() {
empty = true;
boiled = false;
}
public void fill() {
if (isEmpty()) {
empty = false;
boiled = false;
}
}
public void drain() {
if (!isEmpty() && isBoiled()) {
empty = true;
}
}
public void boil() {
if (!isEmpty() && !isBoiled()) {
boiled = true;
}
}
public boolean isEmpty() {
return empty;
}
public boolean isBoiled() {
return boiled;
}
}
public class ChocolateBoiler {
private static ChocolateBoilerSingleton cb;
private boolean empty;
private boolean boiled;
private ChocolateBoiler() { empty = true;
boiled = false;
}
public static ChocolateBoilerSingleton getInstance(){
if(cb == null)
cb = new ChocolateBoilerSingleton();
return cb;
}
public void fill() {
if (isEmpty()) {
empty = false;
boiled = false;
}
}
public void drain() {
if (!isEmpty() && isBoiled()) {
empty = true;
}
}
public void boil() {
if (!isEmpty() && !isBoiled()) { boiled = true;
}
}
public boolean isEmpty() {
return empty;
}
public boolean isBoiled() {
return boiled;
}
}
싱글턴 패턴의 정의
- 싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 만들어진다,
- 어디서든지 그 인스턴스에 접근할 수 있도록 한다.
- 클래스에서 자신의 단 하나뿐인 인스턴스를 관리하도록 만들면 된다.
문제가 생겼다.
도대체 무슨 일이 일어난 거지? 새로 만든 싱글턴 코드가 얼마 전까지만 해도 문제 없이 잘 돌아가고 있었는데.
조금 전에 다중 스레드를 사용하도록 초콜릿 보일러 컨트롤러를 최적화시킨 걸 빼면 이런 문제를 일으킬 만한 게없는데...
두 개의 스레드에서 여기에 있는 코드를 실행시킨다고 가정해 보면.
두 스레드가 다른 보일러 객체를 사용하게 될 가능성은 없는지 따져 보자.
ChocolateBoiler boiler = ChocolateBoiler.getInstance(); fill(); boil(); drain();
멀티스레딩 문제 해결 방법
- getInstance()를 동기화시키기만 하면 멀티스레딩과 관련된 문제가 간단하게 해결된다.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
이렇게 하면 문제가 해결되긴 하겠지만, 동기화를하면 속도 문제가 생기지 않나?
동기화는 불필요한 오버헤드만 증가시킬 수 있다.
더 효율적인 방법은 없을까요?
1. getInstance()의 속도가 그리 중요하지 않다면 그냥 내비 둔다.
- 메소드를 동기화하면 성능이 100배 정도 저하된다는 것은 기억해 두자
- 만약 getInstance( )가 애플리케이션에서 병목으로 작용한다면 다른 방법을 생각해봐야 한다.
2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
이런 접근법을 사용하면 클래스가 로딩될 때 JVM에서 Singleton의 유일한 인스턴스를 생성해 준다.
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;
}
}
핵심 정리
- 어떤 클래스에 싱글턴 패턴을 적용하면 애플리케이션에 그 클래스의 인스턴스가 최대 한 개 까지만 있도록 할 수 있다.
- 싱글턴 패턴을 이용하면 유일한 인스턴스를 어디서든지 접근할 수 있도록 할 수 있다.
- 자바에서 싱글턴 패턴을 구현 할 때는 private 생성자와 정적 메소드, 정적 변수를 사용 한다.
- 다중 스레드를 사용하는 애플리케이션에서는 속도와 자원 문제를 파악해보고 적절한 구현법을 사용해야 한다.
- DCL을 사용하는 방법은 자바2 버전 5(자바 1.5)보다 전에 나온 버전에서는 쓸 수 없다는 점에 주의.
- 클래스 로더가 여러 개 있으면 싱글턴이 제대로 작동하지 않고, 여러 개의 인스턴스가 생길 수 있다.
--------------------------------------------------------------------------------------------------------
О situation : 초코릿 공장
초코릿 끓이는 장치를 컴퓨터로 제어한다.
보일러에서 초콜릿과 우유를 받아서 초코바를 만드는 단계로 넘겨준다.
재료를 넣고 끓이고 비우고 하는 단계를 순서대로 거쳐야 하는데 여러 개의 객체가 생성되면 해당 작업의 동기화가
맞질 않아 단계가 뒤죽박죽이 되어 버린다.
즉 각각의 동작들이 동기화가 되어야만 한다는 말이다.
О point : 한 개의 인스턴스만 생성하는 객체를 만든다.
객체의 생성자를 private 로 선언한다,
인스턴스를 생성하여 반환하는 static 메소드를 선언한다.
New 부분을 동기화하여 상호배재 현상에 대응한다.
<?xml:namespace prefix = o />
О solution :
싱글턴 1 >
Public class Singleton {
Private volatile static Singleton uniqueInstance;
Private Singleton(){}
Public static Singleton getInstance(){
If(UniqueInstance == null){
//메소드 전체를 동기화 시키지 않기 때문에 UniqueInstance 가 생성되지 않은
//처음 1번 빼고는 오버헤드를 발생시키지 않는다.
Synchronized (Singleton.class){
If(UniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
}
}
싱글턴2 >
Public class Singleton {
Private volatile static Singleton uniqueInstance = new Singleton();
Private Singleton(){}
Public static Singleton getInstance(){
return uniqueInstance;
}
}
처음에만 동기화하여 오버헤드를 줄인다. uniqueInstance 가 null이 아니면 동기화 되지 않는 것이다.
О thinking :
전역변수 vs 싱글턴
전역변수를 사용할 경우 처음부터 끝까지 인스턴스를 가지고 있어야 하므로 자원의 낭비가 심하다.
해당클래스가 하나만 만들어지고 어디서든지 그 인스턴스에 접근 할 수 있도록 한다.
새로운 인스턴스가 생성되는 것이 아니란 것이다.
싱글턴이 쓰이는 경우
->>>>>>>>>>>
1) 스레드 풀 (서버 프로그램 할때 데몬 스레드 만들어본 경험있을 것이다)
2) 사용자 설정, 레지스트리 설정등의 환경설정 관련 객체
3) 로그 기록용 객체, 프린터, 그래픽 카드 같은 디바이스 드라이버
----------------------------------------------------------------------------------------------------
Java 를 사용하여
Singleton 패턴을 구현할 경우 주의해야 할 사항이 있다.
멀티 쓰레드 환경에서 하나 이상의 객체가 생성되는 현상이 발견된다는 것이다.
이 현상을 어디서 접하게 되었는고 하니... Tomcat 기반의 웹 어플리케이션을 개발하면서 Singleton 패턴으로 커넥션풀 라이브러리를 만들어 두었다. 여러분도 알다시피 Tomcat에는 클래스파일이 변경되었는지 자동으로 감지하는 "
Watch Dog" 이라는 기능이 탑재되어 있어서, 클래스의 변경을 감지하여 웹 어플리케이션(컨텍스트)을 다시 로딩하는 작업을 수행한다. 당시 데이터베이스의 커넥션 자원을 너무 많이 소모하는 것 같아서 디버깅 작업에 착수하였는데, WatchDog에 의하여 클래스를 다시 로딩할 때 Singleton 패턴으로 정의한 클래스의 객체가 여러 개 생겼음을 발견하게 되었다
!!!
당연히 Singleton 패턴을 사용하면 같은 JVM 상에서는 오직 하나만 존재할 것으로 생각했던 내게 큰 충격이 아닐 수 없었다. 하여 문제를 해결하고자 검색해보니 멀티 쓰레드 환경에서 이런 문제가 꽤나 자주 있었음을 알 수 있었으며, 다음의 두 아티클을 찾을 수 있었다. 이 글들은 Java에서의 Singleton 패턴 구현 방식에 대하여 가장 잘 정리하고 있다:
- http://www-106.ibm.com/developerworks/java/library/j-dcl.html?dwzone=java
- http://c2.com/cgi/wiki?JavaSingleton
첫 번째 자료는 IBM 에 게시된 자료인데 그냥 참고로 읽고, 주로 두 번째 자료를 숙독하기 바란다. 내가 두 번째 자료를 접했을 당시에는 아래와 같은 해결책이 제시되어 있었다.
public class Singleton { private static final Singleton _theInstance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return _theInstance; } }위 해결책은 Darren Hobbs 가 제안한 방법으로 Java Spec에 근거하여 제시한 방법이다. 하지만 Adam 은 이 방식이 거의 대부분의 상황에 잘 적용되긴 하지만 Tomcat 과 JBoss 두 컨테이너에서 테스트 해보니 생성자(constructor)에서 멤버 변수를 초기화하지 못하는 문제가 발견되었다고 문제를 제기하였다.
최근에 다시 두 번째 링크를 방문해 본 결과, Josh Bloch의 "
Effective Java" 책 저술작업에 기여했던 Scot Floess 라는 사람이
David Geary가 JavaWorld에 게재한 글에 대해 Thread-safety 문제를 지적하며 제시한 해결책을 접할 수 있었다(※ 참고 :
http://www.javaworld.com/javaworld/jw-05-2003/jw-0530-letters.html). 일단 Darren Hobbs 가 제안한 방법과 거의 같지만,
1) 클래스 선언 시 final 키워드를 붙여 상속이 불가능하도록 하였으며,
2) static 의 inner class를 사용하여 Singleton 패턴의 클래스를 감싸고 있는 형태가 다르다. 분명 이런 형태로 Singleton 패턴의 클래스를 생성하는 작업은 그 자체로 매우 무거운 작업이긴 하지만, 간단하면서도 완벽한 방식의 Singleton 패턴 구현 방식이라고 한다. 자세한 것은 링크를 참고하도록 하자.
public final class Singleton { private static final class SingletonHolder { static final Singleton singleton = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return SingletonHolder.singleton; } }--------------------------------------------------------------------------------------------------------
전역 변수 vs Singleton
단순 static과 싱글턴의 차이점은:싱글턴이 static 객체로 구현되더라도, 메모리나 자원을 필요로 하기전까지 기다렸다 생성하는 게으른 초기화를 할 수 있다는 것입니다.
또다른 차이는, static 크래스는 interface를 구현할 수 없습니다.
interface에 의해 표현되는 클래스를 구현하기 위해선 싱글톤 패턴으로 만들어야 합니다.
--------------------------------------------------------------------------------------------------------
싱글톤 은 디자인 패턴 중에 한 가지의 방법입니다.
디자인 패턴이란 선배 개발자들의 개발 방법 중 괜찬타 싶은 방법에 이름을 붙여 놓은 것입니다. 즉. 패턴들은 자바라는 언어뿐만 아니라 씨 씨플 씨샵 파이톤 등등 여러 곳에서 응용되어질 수 있는 방법입니다.
그럼 싱글톤 디자인 패턴은 어떤 개발 방법일까요?
우리는 객체를 생성할때 new 라는 키워드를 사용합니다
A a=new A();
이렇게 객체를 생성하면
heap 영역에는 A 클래스의 인스턴스(생성된 객체)가 생성 되어 지고
그 A 클래스의 인스턴스 를 가르 키는 a 라는 변수가 스택에 저장 되어 집니다.
그리고
A a=new A();
A b=new A();
를 할경우엔
이런식으로 메모리 상에 올라 오게 됩니다.
한 가지 유념해야 할것이 있습니다. 힙 영역에 객체가 올라오고 내려 올때 시간이 많이 걸립니다.
그리고 스택 저 화살표 연결 고리가 끊어지면 GC(가비지 컬렉션) 의 대상이 되는데 GC 수거 작업이 진행될시 컴퓨터가 멈춰 질정도로 느려지기 도 합니다.
그래서 자주 사용되는 객체를 생성할 땐
이런식으로 heap 영역에 한개만 올려놓고 스택에선 같은 객체를 가르키도록 코딩하는 것이 바로 싱글톤 디자인 패턴입니다.
저런 식으로 해놓을 경우 힙영역에 객체가 올라 가고 내려가는 시간을 줄이고 또한 힙영역의 메모리 역시 쓰레기 객체들을 줄일수 있는 방법이 됩니다.
그러나 ~ 한가지 조심할 점이 있습니다.
a와 b 가 똑같은 객체를 가르키고 있기 때문에 A 클래스의 멤버 변수 역시 공유한다는 사실입니다.
즉 a 에서 멤벼 변수값을 바꾸게 되면 b 의 멤버 변수 역시 값이 바뀌게 되어 이점 유념하여서 싱글톤 디자인 패턴을 사용할 객체 인지 판별을 해야합니다.
즉 싱글톤 패턴은 멤버 변수가 없이 멤버 함수들만 으로 구성되어진 클래스 들에서 많이 사용되어 집니다
---싱글톤 디자인 패턴을 적용한 객체 만드는법--
여러가지 방법이 있으나 가장 간단한 방법을 알려드리겠습니다.
public class A{
static A aInstence=new A();
public static A getInstence(){
return aInstence;
}
private A(){
}
}
---------
바로 위의 방법입니다
위 의 방법으로 설계된 객체를 생성할 때는
A a=new A();
로 하면 에러가 나게 됩니다. 왜냐마혐 생성자가 private 로 되어 있기 때문입니다.
그래서 스태틱 변수인 getIntence() 라는 메서드를 통해서 인스턴를 받아야합니다
A a=A.getInstence();
A b=A.getInstence();
이렇게 할경우 힙영역에 A 인스턴스는 하나올라 오게 되고, a 와 b 는 같은 인스턴스를 가리키게 됩니다.