본문으로 바로가기

참고)

싱글톤 패턴(Singleton pattern) 실제 예제는 https://codehouse.tistory.com/10 에서 확인할 수 있다. 

 

 

1. 싱글톤패턴(Singleton Pattern) 정의

 

1-1 특징

인스턴스가 하나만 생성되는 것을 보장하여, 어느곳에서나 인스턴스에 접근가능한 클래스 메서드(getInstance()) 가지고 있는  패턴이다.

getInstance() 메서드를 통해 모든 클라이언트에게 동일한 인스턴스를 제공할 있다.

 

1-2 장점 및 효과

프로그램 상에 하나만 존재해야 하는 객체가 필요할 사용한다. 운용하는 객체가 하나이다 보니 관리의 집중도가 높아져 유지보수에 도움이 된다최초 객체 생성이후에는 객체 로딩시간이 많이 줄어든다.(이미 메모리에 할당이 되어 있기때문에)

 

1-3 단점

싱글톤 패턴역할을 객체가 너무 많은 일을 하면 해당 객체를 공유하는 클라이언트의 결합도가 너무 높아진다. (OOP 원칙에 어긋남 - 느슨한 결합). 결합도가 너무 높아지게 되면 해당 싱글톤객체를 수정해야할 시 해당 객체를 사용하는 모든 클라이언트에 영향을 주어 오히려 유지보수에 악영향을 끼칠 수 있다.

 

1-4 사용예

프로그램 선택자, 사용자 설정을 처리하는 객체, 와이파이, 블루투스 등록 등 시스템 레지스트리 등록자

 

 

 

2. 싱글톤패턴(Singleton Pattern) 구현 방식

 

2-1 고전적 방식

고전적인 싱글톤 패턴(singleton pattern)의 구현법은 다음과 같다.

 

)

ClassicalSingleton.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ClassicalSingleton {
 
    private static ClassicalSingleton singleton;
    
    //생성자도 private으로 지정하여 접근하지 못하도록 설정한다.
    private ClassicalSingleton() {}
    
    public static ClassicalSingleton getInstance() {
        
        //객체가 지정되어 있지 않다면 객체를 생성하여 준다.
        if(singleton == null) {
            singleton =  new ClassicalSingleton();
        }
        
        return singleton;
    }
    
    public void getClassAddress() {
        System.out.println(singleton);
    }
}
 

 

usingSingleton.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class usingSingleton {
 
    public static void main(String[] args) {
        ClassicalSingleton singletonNum1 = ClassicalSingleton.getInstance();
        singletonNum1.getClassAddress();
        
        ClassicalSingleton singletonNum2 = ClassicalSingleton.getInstance();
        singletonNum2.getClassAddress();
    
        
        ClassicalSingleton singletonNum3 = ClassicalSingleton.getInstance();
        singletonNum3.getClassAddress();
    }
 
}
 
 


결과)

ClassicalSingleton@7852e922
ClassicalSingleton@7852e922
ClassicalSingleton@7852e922

생성자를 private로 선언하여 객체를 선언할 수 없도록 하였으며, 필요한 상황이 올때 getInstance()메서드를 호출해서 사용하도록 하였다.

이와 같이 인스턴스를 미리 만들어 놓지 않고, 필요한 상황에 생성하는 방식을 게으른 인스턴스 생성이라 한다.(lazy Instantiation)

또한 결과를 보고 알수 있듯이 인스턴스를 어느 객체변수에 생성 시켜도 동일한 객체를 사용한다는 것을 알 수 있다.

하지만 이 방식은 멀티스레드 방식에서 인스턴스가 2개이상 생성될 수 있다는 문제가 있다.

 

 

 

 

2-2 멀티 스레드(Multi Thread) 에서의 싱글톤 패턴(Singleton pattern) 구현방식

 

2-2-1 Synchronized 방식

고전적 방식에서 getInstance() 메서드에 동기화 키워드인 synchronized 사용하면 된다.

 

)

syncSingleton.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class syncSingleton {
    private static syncSingleton singleton;
 
    //생성자도 private으로 지정하여 접근하지 못하도록 설정한다.
    private syncSingleton() {}
 
    public static synchronized syncSingleton getInstance() {
 
        //객체가 지정되어 있지 않다면 객체를 생성하여 준다.
        if(singleton == null) {
            singleton =  new syncSingleton();
        }
 
        return singleton;
    }
 
    public void getClassAddress() {
        System.out.println(singleton);
    }
}
 
 

 

스레드가 객체를 생성하고 사용이 끝날때까지 다른 스레드는 인스턴스 생성을 기다려야 한다. 이방법을 이용하면 확실히 동시에 인스턴스가 생성되는것을 방지 있다 . 하지만 synchronized 사용함으로써 해당 어플리케이션의 속도가 느려진다는 단점이 있다. (심각한 수준의 성능저하)

 

 

2-2-2 DCL(Double Checking Locking)방식

 

예)

syncSingleton.java

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
public class syncSingleton {
    private volatile static syncSingleton singleton;
 
    //생성자도 private으로 지정하여 접근하지 못하도록 설정한다.
    private syncSingleton() {}
 
    public static syncSingleton getInstance() {
 
        //인스턴스가 생성중인지 확인한다.
        if(singleton == null) {
            //동기화를 사용하여 다른 스레드가 사용중인지 확인
            synchronized (syncSingleton.class) {
                // 다시한번 인스턴스가 생성되어 있는지 확인한다.
                if(singleton == null) {
                    singleton =  new syncSingleton();
                }
            }
        }
 
        return singleton;
    }
 
    public void getClassAddress() {
        System.out.println(singleton);
    }
}
 

 

getInstance()에서 동기화를 사용하는것이 아니라, 인스턴스를 생성할 동기화를 사용하므로, 처음 인스턴스를 생성할 이후에 synchronized 타지 않으므로 성능저하의 단점을 커버할 있다. 다만 방법은 shared-memory  방식에서 안정성이 떨어질 있다.

 

 

2-2-3 Initialization on demand holder idiom (holder에 의한 초기화)

클래스 안에 holder클래스를 두어 JVM의 클래스 초기화 시점에서 class를 생성하는 방식이다. 구현방식은 다음과 같다.

 

예)

holderSingleton.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class holderSingleton {
 
    private holderSingleton() {}
 
    //정적 class(lazyholder)생성 시 로딩 시에 인스턴스를 생성한다.
    private static class lazyholder {
        private static final holderSingleton singleton = new holderSingleton();
    }
    
    public static holderSingleton getInstance() {
        return lazyholder.singleton; 
    }
    
    public void getClassAddress() {
        System.out.println(lazyholder.singleton);
    }
}
 
 

 

usingSingleton.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class usingSingleton {
 
    public static void main(String[] args) {
        holderSingleton singletonNum1 = holderSingleton.getInstance();
        singletonNum1.getClassAddress();
        
        holderSingleton singletonNum2 = holderSingleton.getInstance();
        singletonNum2.getClassAddress();
        
        holderSingleton singletonNum3 = holderSingleton.getInstance();
        singletonNum3.getClassAddress();
    }
 
}
 
 

결과)

holderSingleton@7852e922
holderSingleton@7852e922
holderSingleton@7852e922

holder클래스 안의 인스턴스가 static이기 때문에 클래스 로딩시점에 한번만 생성되며 final이기 때문에 할당이 되는것을 방지한다현재 방식이 제일 널리 사용되고 있다.