제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



자바 디자인 패턴 5 - Singleton

1. Singleton 패턴은..

각종 설정 등이 저장된 클래스가 하나 있다고 칩시다. 프로그램 내에서 여기저기서 마구 접근해서 설정을 바꾸기도 하고 값을 가져오기도 합니다. 이런 클래스는 인스턴스를 하나만 가져야 합니다. 하나 만들어서 쓰는 곳마다 인자로 전달해주면 되긴 합니다만, 접근하는 곳이 많다면, 계속 인자로 전달하는 것은 그다지 바람직하지 않습니다. 전역변수처럼 아무곳에서나 이 인스턴스에 접근을 하면 편하겠죠. Singleton 패턴을 이용하면, 하나의 객체를 만들어서 아무데서나 접근할 수 있습니다.

2. 예제

---------------------  Singleton으로 구현된 클래스 ----------------
package ch05_Singleton;

public class SingletonCounter {
    private static SingletonCounter singleton = new SingletonCounter();
    private int cnt = 0;
    private SingletonCounter(){
    }

    public static SingletonCounter getInstance(){
        return singleton;
    }

    public int getNextInt(){
        return ++cnt;
    }
}
---------------------- 테스트 클래스 ---------------------
package ch05_Singleton;

public class Test {
    public static void main(String[] args) {
        Test t = new Test();
        t.Amethod();
        t.Bmethod();
    }
    public void Amethod(){
        SingletonCounter sc = SingletonCounter.getInstance();
        System.out.println("Amethod에서 카운터 호출 " + sc.getNextInt() );
    }
    public void Bmethod(){
        SingletonCounter sc = SingletonCounter.getInstance();
        System.out.println("Bmethod에서 카운터 호출 " + sc.getNextInt() );
    }
}

---------------------- 실행 결과 -----------------------
Amethod에서 카운터 호출 1
Bmethod에서 카운터 호출 2


singleton에서 중요한 것은 다음 세 가지입니다.
첫째, private 멤버 변수로 자기 자신의 클래스의 인스턴스를 가집니다. 황토색 부분입니다.
둘째, private 생성자를 지정하여, 외부에서 절대로 인스턴스를 생성하지 못하게 합니다. 보라색 부분입니다.
셋째,getInstance() 메쏘드를 통해 객체를 static하게 가져올 수 있습니다. 파란색 부분입니다.

이는 유일무이한 인스턴스를 만들기 위해 생긴 규약들입니다. 무슨 수를 써도 Singleton 클래스를 수정하지 않는 한 새로운 인스턴스를 만들 수 없습니다.

3. Singleton을 구현하는 몇 가지 방법

------------- 첫번째 --------------
package ch05_Singleton;

public class Singleton1 {
    private static Singleton1 single = new Singleton1();
    public static Singleton1 getInstance(){
        return single;
    }
    private Singleton1(){
    }
}

클래스 로드시 new가 실행이 됩니다. 항상 1개의 인스턴스를 가지게 되겠죠. 코드가 가장 짧고 쉽습니다. 성능도 다른 방법에 비해 좋습니다.

-------------- 두번째 --------------
package ch05_Singleton;

public class Singleton2 {
    private static Singleton2 single;
    public static synchronized Singleton2 getInstance(){
        if (single == null) {
            single = new Singleton2();
        }
        return single;
    }
    private Singleton2(){
    }
}

클래스 로드시에는 인스턴스가 생성되지 않습니다. getInstance()가 처음 호출될 때 생성이 되지요. 그러나 synchornized가 걸려 있어서 성능이 안 좋습니다. 인스턴스를 사용할 필요가 없을 때는 인스턴스가 생성되지 않는다는 점이 첫번째 방벙에 비해 장점입니다.

--------------- 세번째 ---------------
package ch05_Singleton;

public class Singleton3 {
    private volatile static Singleton3 single;
    public static Singleton3 getInstance(){
        if (single == null) {
            synchronized(Singleton3.class) {
                if (single == null) {
                    single = new Singleton3();
                }
            }
        }
        return single;
    }
    private Singleton3(){
    }
}

첫번째의 장점인 성능이 좋다(synchronized 가 안 걸려서)와 두번째의 장점인 안 쓸 때는 인스턴스를 아예 만들지 않는다의 장점만 뽑아온 방법입니다. 코드는 제일 깁니다^^.
여기서 중요한 점은 if(single == null) 을 두 번이나 체크합니다. A, B 2개의 thread가 접근을 한다고 가정합니다.
A와 B가 거의 동시에 들어와서 바깥쪽 single== null 인 부분을 통과했다고 칩시다. 그리고 A가 조금 먼저 synchronized 블럭에 진입했습니다. B는 그 앞에서 대기 중이지요. A가 다시 single== null을 체크합니다. 여전히 null이지요. 그러면 인스턴스를 만들고 synchronized 블럭을 탈출합니다. 그러면 B가 synchronized 안으로 진입합니다. single은 더 이상 null이 아닙니다. A가 만들었으니까요. B는 그냥 synchronized 블럭을 빠져나옵니다.
바깥쪽 if(single == null) 가 없다면, 성능 저하가 발생합니다. 매번 synchronized 블럭 안으로 들어가니까요. 두번째 방법과 같다고 보시면 됩니다. 안쪽의 if(single == null) 가 없다면, singleton이 보장되지 않습니다.
volatile 키워드도 꼭 써줘야 합니다. volatile 키워드는 변수의 원자성을 보장합니다. single = new Singleton3(); 이란 구문의 실행은 원자성이 아닙니다.(원자성이란 JVM이 실행하는 최소단위의 일을 말합니다. 즉 객체 생성은 JVM이 실행하는 최소단위가 몇 번 실행되어야 완료되는 작업이란 뜻입니다.)  JVM에 따라서 single이라는 변수의 공간만을 먼저 생성하고 초기화가 나중에 실행되는 경우도 있습니다. 변수의 공간만 차지해도 null은 아니기 때문에 singleton이 보장된기 어렵습니다. JVM 버전이 1.4(어쩌면 1.5 잘 기억이..--;; ) 이전에서는 volatile 키워드가 정상적으로 작동하지 않을 수도 있다고 합니다.

--------------- 네번째 ---------------
package ch05_Singleton;

public class Singleton4 {
    private Singleton4(){
    }
    private static class SingletonHolder{
        static final Singleton4 single = new Singleton4();
    }
    public static Singleton4 getInstatnce(){
        return SingletonHolder.single;
    }
}

네번째 방법은 내부 클래스를 사용하는 방법입니다. 기존의 3가지 방법에서는 Singleton 클래스가 자기 자신의 타입을 가지는 멤버 변수를 가지고 있는데, 네번째의 경우는 내부 클래스가 가지고 있습니다. 내부 클래스가 호출되는 시점에 최초 생성이 되기 때문에, 속도도 빠르고 필요치 않다면 생성하지도 않습니다.

4. Singleton의 특징

Singleton은 당연히 인스턴스가 1개만 생깁니다. 그러자고 만든 거니까요. 또 하나의 규약은 private 생성자 때문에 상속이 안 된다는 점입니다. (상속받은 하위체는 상위체의 생성자를 호출합니다.) 예를 들어 Singleton에서 설정관련된 xml 파일을 수정한다고 칩시다. 상속을 받아 다른 객체를 만들어서 파일을 수정하는 시도를 하면 안되지요. 상속을 받게 되면 "인스턴스 1개"라는 원칙을 깨게 됩니다.
private 생성자는 외부에서의 직접호출을 통한 생성을 막는 것과 상속을 막는 두 가지 기능을 수행합니다. 둘 다 "인스턴스 1개"라는 원칙을 지키는 것이죠.

Factory 패턴과 사용법이 매우 유사합니다. Singleton은 Factory의 특이 케이스로 볼 수도 있습니다. Factory는 매번 객체를 만들어서 리턴하는 방법이고 Singleton은 한 개만 만들어서 요청이 들어올 때마다 만들어진 객체를 리턴한다는 게 차이점입니다. 또 일반적으로 Factory는 create...과 같은 메쏘드 이름을 사용하고 Singleton은 getInstance라는 메쏘드 이름을 사용합니다.

위에서 말한 세가지 방법 중 첫번째 방법의 경우는 public으로 멤버 변수를 선언하고 외부에서 직접 변수에 접근해서 사용하게 해도 됩니다. (반드시 private이어야할 필요는 없다는 거죠. ) 두번째와 세번째는 초기화가 보장이 안 되어 있지만, 첫번째의 경우는 보장되어있기 때문입니다. 주의할 점은 외부에서 악의적으로 public 멤버 변수는 바꿔치기를 할 수도 있기 때문에 이런 식으로 접근할 때는 final 을 붙여주는 게 좋습니다.(어차피 private 생성자를 가지고 있으니, 외부에서 새로운 객체를 만들어 낼 수는 없지만 null을 대입할 수는 있기 때문에 final이 필요합니다.) 그럼 public static final이 되는군요! 상수란 말이죠. 하지만 일반적인 상수와는 다릅니다. 일반적인 상수는 Immutable 로 구현이 되어있기 때문입니다. 상수로 많이 쓰는 String, Integer, Boolean 등은 전부 Immutalbe입니다.
물론 이런 접근이 권장사항은 아닙니다. 그냥 가능하긴 하다는 얘깁니다.

5. JAVA API에 있는 Singleton

Boolean에 있는 valueOf 들은 전부 Singleton 비스무레하게 구현되어 있습니다. 다만 인자를 받기 때문에 멤버 변수로 예제처럼 1개만 가지고 있는 것이 아니라 여러개를 가질 수 있습니다. true라는 값을 가지는 Boolean과 false라는 값을 가지는 Boolean 객체 2개가 존재하는 것이죠.
Collections에 있는 empty.. 하는 메쏘드들도 전부 Singleton입니다.

jdk 안에 있는 Singleton은 대부분 위에서 말한 방법 중 첫번째 방법을 쓰고 있습니다. 클래스 로드시 멤버 변수들을 초기화하는 방법입니다. 그래서 대부분 그 멤버 변수들은 public static final 로 선언되어있습니다.

by 삼실청년 | 2007/09/27 18:14 | 컴터질~ | 트랙백 | 핑백(4) | 덧글(24)

트랙백 주소 : http://iilii.egloos.com/tb/3807664
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at 건실성실착실 3실 청년! : .. at 2008/06/16 22:56

... nbsp;String b="머시기";와 같이 동일한 값이 다시 호출될 때는 풀에 등록된 값을 리턴해 줍니다. Singleton으로 처리된다고 이해하면 되겠습니다.(Singleton 은 여기와 여기를 읽어보시면 됩니다.)2번의 경우는 new String으로 String 객체를 생성하기 때문에 일반적인 객체 생성과 같은 맥락입니다. ==는 메모리상의 주소만 비교하지 ... more

Linked at 베르이야기 » 자바.. at 2014/01/13 16:42

... http://iilii.egloos.com/3807664 ... more

Linked at java design patt.. at 2017/05/31 09:25

... 5. 자바 디자인 패턴 5 – Singleton</A> 6. 자바 디자인 패턴 6 – Strategy 7. 자바 디자인 패턴 7 – Composite 8. 자바 디자인 패턴 8 – Decorator 9. 자바 디자인 패턴 9 <a href="http://blog.sina.com.cn/s/blog_8e40db350102wt27.html">港股开户 &#8211; Chain of Responsibility ... more

Linked at 자바 디자인 패턴 종류 &#8.. at 2017/09/26 09:15

... . 자바 디자인 패턴 3 &#8211; Factory Method 4. 자바 디자인 패턴 4 &#8211; Template Method 5. 자바 디자인 패턴 5 – Singleton 6. 자바 디자인 패턴 6 – Strategy 7. 자바 디자인 패턴 7 – Composite 8. 자바 디자인 패턴 8 – D ... more

Commented by 강하오 at 2008/03/31 10:33
좋은 글 감사드립니다.

근데 한가지 궁금한게 있어서요.

Singleton을 왜 굳이 따로 만드는지요? 그냥 모든 필드, 메소드를 static으로 하면 되는거 아닌가요?

상속만 못하게 막구요.
Commented by 삼실청년 at 2008/04/02 01:12
말씀하신 것이 맞습니다. 그러나 static이란 것이 사실 OOP와 잘 맞지 않는 컨셉입니다. static이 난무하는 프로그램을 한 번 짜보시면 아시겠지만, 정신을 차릴 수가 없습니다. 프로그램의 복잡도를 엄청나게 증가시키죠.
그러나, static이 가지는 장점 또한 있으니, 그것을 또 버릴 수는 없습니다. 그에 대한 합의점을 적당히 찾아가는 것이 필요하죠. Singleton은 대략 그 합의점 정도라고 보시면 될 것 같습니다.(객체 생성까지만 static으로 하고 나머지는 static으로 쓰지 않겠다는 컨셉정도 라고 보시면 될 듯합니다.)
Commented by 삼실청년 at 2008/04/15 22:49
아.. 또 한 가지 중요한 문제는 Serialize에 관련된 문젭니다. 이건 여기다가 설명하기엔 양이 너무 많습니다. 간단하게 얘기하면 인스턴스가 아니라 클래스를 직접 Serialize하면 받아서 쓰기가 어렵습니다. 여기서는 설명하지 않았지만, Singleton의 경우 Serialize되어야 할 상황이라면 몇 가지 신경써야 할 부분들이 더 있습니다. Serialize와 Singleton에 대한 얘기는 다음에 정리하지요.
Commented by 해피곰 at 2010/02/25 17:45
Singleton 클래스는 생성자에 파라미터를 갖을 수 없나요?
Singleton2 single = new Singleton2(param); 이렇게요. 초기 생성이 안되서 안되겠죠?
Commented by 삼실청년 at 2010/02/28 22:01
생성자 자체가 private이니까, 불가능하죠.
초기화 로직을 이용하고 싶으시다면, static으로 init 메쏘드를 만들고, getInstance 메쏘드에서 initialize 체크를 하는 방법도 가능합니다.
Commented by 혜연 at 2011/04/06 11:03
좋은글 감사드립니다^^ 싱글톤에 대해 이해하는데 늘 2% 모자랐었는데 남은부분을 채워주셨어요~
Commented by 삼실청년 at 2011/04/15 19:40
^^ 도움이 되셨다니 다행~~
Commented by 회색부엉이 at 2012/08/21 02:28
좋은 정보 감사합니다. ^^
Commented by 삼실청년 at 2012/10/06 00:06
답글 달기에는 너무 늦었나요?...
버려진 블로그라....
좋은 하루 되세요^^
Commented by 보드보드 at 2012/11/15 08:52
감사합니다!!
Commented by 삼실청년 at 2012/11/19 21:01
고맙습니다!!
Commented by 라온수리 at 2012/11/23 14:56
싱글톤패턴을 사용하면서 생성자를 private해서 생성을 new 를사용하여 새로운 객체를 사용하지 못하게 막고 어디서든지 사용할수 있게끔 static을사용하여 getInstance() 메서드를호출하여 하나의객체만 사용하는법 잘봤습니다 감사드려요 이해잘되게 설명 너~~~~~무 자세하게 해주시네요 최고입니다!!
Commented by 삼실청년 at 2012/11/23 22:30
남들이 정리해놓은 거 걍 다시 정리한거에요.
찾아주셔서 감사합니다^^
Commented by 우왕ㅋㅋㅋ at 2013/09/12 09:41
우와!!! 감사합니다 ㅋ
Commented by 삼실청년 at 2013/09/15 22:02
도움이 마구 되셨길 바랍니다^^
Commented by 서천 at 2014/01/02 16:54
진짜 한눈에 쏙쏙 들어오는 쪽집게 과외 받는기분이었습니다. 좋은 정보 감사드립니다.
Commented by 삼실청년 at 2014/01/24 00:43
도움이 되셨길 바래요^^
Commented by 멋지심 at 2014/01/13 11:02
싱글톤에 대한 정리를 아주 잘 하셨네요.
블로그에 출처 남기고 퍼가겠습니다.
Commented by 삼실청년 at 2014/01/24 00:43
anti-nhn 만 준수해주세요^^ nhn이 분사했다고 하던데... 라이센스 고쳐야 되나... 싶네요ㅋ.
Commented by 롸빈 at 2014/05/23 09:51
오아시스 같은분.
존경합니다.
Commented by 삼실청년 at 2014/06/14 02:12
부끄럽네요.ㅎ
도움이 되셨길 바래요.
Commented by eizt at 2015/07/10 11:25
오오 감사합니다. !!!
Commented by 탱리둥절 at 2015/12/11 00:55
너무 친절한설명 감사드립니다!
궁금한것이 하나 있어서 댓글답니다!
네번째 구현에서

private static class SingletonHolder{
static final Singleton4 single = new Singleton4();
}

위 싱글톤홀더 클래스와 안에 있는 single도 둘다 static이기 때문에 프로그램이 처음 실행될때
메모리에 로딩되는 것이 아닌가요?!

내부클래스가 호출되는 시점에 최초 생성된다는것이 잘 이해가 안갑니다!
Commented by 굼긍 at 2016/01/10 22:53
프로그램 실행 시에 single이라는 instance가 바로 메모리에 loading되는 것이 아닙니다.

class는 누군가가 해당 class에 어떤 동작을 취할 때
class loader에 의해 loading됩니다.
이는 nested class에도 동일하게 적용됩니다.
또, class 내부의 static 변수 등은 class가 loading될 때 초기화되죠..

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶