지난 시간 Instance 수를 제한하는 Singleton 패턴을 배웠었고, 과제로 한 개의 Instance 만 제한하는 것이 아닌 특정 개수만큼의 Instance 를 제한하는 Multiton 을 만들어 보도록 하였습니다.


관련 내용은 아래 포스팅을 참고!



똑똑한 우리 스터디 구성원 모두 과제를 잘해왔고, 정리하는 차원에서 제가 만든 Multiton 을 Review 해보도록 하겠습니다.


일단 가정은 요구사항의 SetInstanceCount 시점에 Instance pool 을 만드는 것이 아닌(Eager-binding), 개수만 먼저 제한하고 필요시점마다 Instance 를 생성하는 게으른 할당(Lazy-binding)으로 제작해 보겠습니다.


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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class Multiton {
    // INSTANCE POOL 관리.
    // 어떤 세팅도 하지 않았으면, Singleton 으로 관리하도록 처리.
    private static int INSTANCE_LIMIT_COUNT = 1;
 
    // Instance pool.
    private static HashMap<Integer, Multiton> POOL = new HashMap<>();
 
    private static Integer toggleCount = 0;
 
    private String toStringMessage;
 
    /**
     * 외부에서 Instance 를 만들지 못하도록 접근제한.
     */
    private Multiton(){
        // Instance 의 생성과 함께 POOL 에 넣는다.
        Integer number = POOL.size();
 
        toStringMessage = String.format("%d번 인스턴스", number);
        POOL.put(number, this);
    }
 
    /**
     * Instance 출력.
     *
     * <pre>
     *     Lock 을 메소드에 모두 걸어 Thread-safe 를 보장.
     * </pre>
     *
     * @return
     */
    public static synchronized Multiton GetInstance() {
        final Multiton result;
 
        if (POOL.size() < INSTANCE_LIMIT_COUNT) {
            // Instance 개수가 아직 제한된 POOL 만큼 생성안된 경우.
            result = new Multiton();
        } else {
            // Instance 개수가 아직 제한된 POOL 만큼 생성 된 경우.
            result = POOL.get(toggleCount);
            toggleCount = (toggleCount + 1) % INSTANCE_LIMIT_COUNT;
        }
 
        return result;
    }
 
    /**
     * 인스턴스의 개수 제한
     *
     * @param limitCount
     */
    public static final void SetInstanceCount(int limitCount) {
        if (limitCount < 0) {
            throw new RuntimeException("[에러] Instance 개수는 0보다 커야 합니다.");
        }
        INSTANCE_LIMIT_COUNT = limitCount;
    }
 
    @Override
    public String toString(){
        return toStringMessage;
    }
}
cs


코드 내부의 구체적인 주석을 명시 하였고, 주의깊게 봐야할 부분은 아래와 같습니다.


1. Thread-safe 보장을 위해 Instance 를 유일하게 외부로 내보낼 수 있는 GetInstance 메소드에 synchronized 키워드를 달았습니다.


2. 사용자가 SetInstanceCount 를 사용하지 않을 수 있기 때문에 Instance 개수 제한을 명시적으로 1로 초기화하였습니다.

(INSTANCE_LIMIT_COUNT = 1)


이렇게 제작된 코드를 Thread-safe 가 보장이 되는지 확인해보도록 하겠습니다.


아래 테스트는 Thread 를 50 개 제작하여, 실행하는 메소드입니다. 각 스레드에서는 각 Instance 의 toStringMessage 를 출력하도록 합니다.


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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Thread Test using JAVA7
{
    System.out.println("멀티톤 테스트!!");
    Multiton.SetInstanceCount(5);
 
    // Thread 목록 생성.
    final ArrayList<Thread> multiThreadTestList = new ArrayList<>();
    {
         for (int i = 0; i < 50++i) {                    
            Thread newThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Multiton.GetInstance());
                }
            });
 
            multiThreadTestList.add(newThread);
        }
    }
 
    // Thread 실행
    {
        // Thread 생성 즉시 바로 실행하지 않고, 목록을 생성하고 실행하는 이유는 최대한 동시에 Thread 를 실행하고 싶기 때문!
        // Thread Instance 를 만드는 시간 때문에 제대로 테스트가 안될 가능성 존재.
        for (Thread thread : multiThreadTestList) {
            thread.start();
        }
    }
}
 
// Thread Test using JAVA8
{
    IntStream.rangeClosed(050).mapToObj(n -> new Thread(() -> System.out.println(Multiton.GetInstance()))).forEach(Thread::start);
}
 
// CONSOLE LOG
// 멀티톤 테스트!!
// 0번 인스턴스
// 1번 인스턴스
// 2번 인스턴스
// 3번 인스턴스
// 4번 인스턴스
// 1번 인스턴스
// 3번 인스턴스
cs


CONSOLE LOG 를 보면, Instance 가 꼭 순서대로 나오지는 않는 것을 볼 수 있습니다. 

비록 GetInstance 에서 LOCK 처리는 하였지만 Thread 의 실행 상태에 따라 순서가 바뀔 수는 있습니다. 

순서를 보장해야만 한다면 Blocking 처리 등 더 복잡한 과정이 필요하겠지만, 이번 주제는 Instance 의 개수를 제한하는 것이니 다루지 않을 예정입니다.


이 블로그가 스터디를 참여하고 있는 모두에게 좋은 정보가 되길 바랍니다. :-)

반응형
Posted by N'

지난 스터디에서 다룬 Singleton 은 Instance 를 한 개로 제한함을 알 수 있었습니다.


이를 이용해서 우리는 Instance 를 두개로 제한하는 아래와 같은 Doubleton 을 구현할 수 있었습니다.


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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
enum Doubleton {
    SINGLE("1번 인스턴스"), DOUBLE("2번 인스턴스");
 
    public static Main.Doubleton GetInstance() {
          // 토글
        a = (a + 1) % 2;
 
        Doubleton result = null;
            
        switch (a) {
        case 0:
            result = DOUBLE;
            break;
        
        case 1:
            result = SINGLE;
            break;
        
        default:
            new RuntimeException(String.format("[에러] 예상못한 TOGGLE 변수 : %d", a));
        }
        
        return result;
    }
 
    private static int a = 0;
    private String toStringMessage;
 
    Doubleton(String toStringMessage) {
        this.toStringMessage = toStringMessage;
    }
 
    @Override
    public String toString(){
        return toStringMessage;
    }
}
 
// TEST CODE
for (int i = 0; i < 100000++i) {
    new Thread(() -> {
        String message = Doubleton.GetInstance().toString();
        System.out.println(message);
    }).
    start();
}
 
// CONSOLE LOG
// 1번 인스턴스
// 2번 인스턴스
// 1번 인스턴스
// 2번 인스턴스
// 1번 인스턴스
// .........
cs


하지만 아쉽게도 enum 이용한 Doubleton 은 Instance 의 갯수를 동적을 조절할 수 없어보이네요. 

우리는 Instance 의 개수를 동적으로 조절할 수 있는 Multiton 을 만들어보는 것이 목적입니다.


아래와 같이 동적으로 미리 Instance 개수를 입력받도록 세팅을 먼저 했으면 좋겠습니다.

물론 Thread-safe 도 보장하면 좋겠죠? 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Multiton.SetInstanceCount(5);
 
for (int i = 0; i < 100000++i) {
    new Thread(() -> {
        String message = Multiton.GetInstance().toString();
        
        System.out.println(message);
    }).start();
}
 
// CONSOLE LOG
// 1번 인스턴스
// 2번 인스턴스
// 3번 인스턴스
// 4번 인스턴스
// .....
cs


그럼 우리 스터디 멤버들 파이팅!!!! :-)

반응형
Posted by N'

이번 주에는 실무에서 아마 가장 많이 사용하는 패턴 중 하나인 Singleton 패턴을 다뤄보았습니다.


자료는 아래 포스팅에서 참고!



물론 웹 프로젝트에서는 직접 Singleton 을 만들기보다는 Bean 등록을 통해 Component 들을 사용하지만, 안드로이드 프로젝트 혹은 자바가 아닌 다른 환경에서는 직접 만들어야 할 일이 있을 수 있습니다.


그렇기 때문에 세 번째 실무 주제로 Singleton 을 선택하였습니다.


Singleton 은 클래스 내에서 한 개의 instance 만 생성하여, 그 인스턴스를 광역적으로 사용하기 위한 패턴입니다. 

(웹 프로젝트에서는 각종 Component 를 Controller 가 광역적으로 사용하고 있습니다.)


1. Singleton 의 기본적인 컨셉


기본적인 Singleton 의 컨셉은 아래와 같이 정의해 볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * 싱글톤 예제
 *
 * Created by Doohyun on 2017. 5. 14..
 */
public class Singleton {
    // 인스턴스는 클래스 내부에서 공용으로 한개만 관리.
    private static Singleton instance = null;
 
    // 외부에서 인스턴스를 생성 못하도록 접근제한
    private Singleton() {}
 
    // 인스턴의 접근 제한은 아래 메소드로만 가능.
    public static Singleton GetInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
 
        return instance;
    }
}
cs


instance 를 한 개만 생성하고 관리하는 것이 목적이므로, 클래스 내부에서 instance 를 공용으로 관리하고 instance 를 접근할 수 있는 정적 메소드를 제공해줍니다.


하지만 이 기본적인 컨셉은 안타깝게도 동시성 처리에 취약합니다.

내부 GetInstance 를 동시에 여러 Thread 가 접근할 시, instance 의 null 체크의 Thread-safe 를 보장할 수 없습니다.



2. Thread-safe 한 Singleton


Singleton 의 Thread-safe 문제를 해결하기 위해 생각해 볼 수 있는 방법은 공용으로 관리하려는 instance 에 대한 명시적 초기화를 해주는 것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 명시적 초기화한 싱글톤 예제
 *
 * Created by Doohyun on 2017. 5. 14..
 */
public class Singleton {
    // 인스턴스의 명시적 초기화
    private static Singleton instance = new Singleton();
 
    // 외부에서 인스턴스를 생성 못하도록 접근제한
    private Singleton() {}
 
    // 인스턴의 접근 제한은 아래 메소드로만 가능.
    public static Singleton GetInstance() {
        return instance;
    }
}
cs


클래스 로드 타임에 미리 instance 가 생성되기 때문에 Thread-safe 합니다. 

이런  방식을 Eager-binding (부지런한 할당)이라 합니다. 하지만 Eager-binding  방식은 싱글톤의 instance 를 사용하지 않음에도 바로 초기화가 되어버릴 수 있습니다.


아래 예제 처럼 말이죠.


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
public class Singleton {
    // 인스턴스의 명시적 초기화
    private static Singleton instance = new Singleton();
 
    // 외부에서 인스턴스를 생성 못하도록 접근제한
    private Singleton() {
        System.out.println("Create Singleton!!");
    }
 
    // 인스턴의 접근 제한은 아래 메소드로만 가능.
    public static Singleton GetInstance() {
        return instance;
    }
 
    /**
     * 싱글톤의 정적 메소드
     */
    public static void RunStaticTask() {
    }
}
 
Singleton.RunStaticTask();
 
// CONSOLE LOG
// Create Singleton!!
cs


하지만 만약 Singletone 을 만들기 위한 비용이 크고, 많은 Singleton 들이 Eager-binding을 시도한다면 부하가 클 수도 있습니다. 즉 Instance 를 사용할 때 생성하는 Lazy-binding(게으른 할당)이 필요해 보입니다.


Eager-binding 이 무조건 나쁜 것은 아닙니다. 서버 프로젝트의 경우 오히려 서버가 켜질 때 각 컴포넌트들이 미리 Instance 를 생성하고, Client 의 요청에 딜레이 없이 바로 응답해주는 것이 좋습니다.


3. Lazy-binding 방식의 Singleton 생성


다시 기본적인 컨셉으로 돌아와서 생각해 봤을 때, 직관적인 방법은 오직 한 개의 스레드 만 접근하도록 Lock 을 걸어주는 것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * 스레드의 Lock 을 이용한 싱글톤 예제 (1)
 *
 * Created by Doohyun on 2017. 5. 14..
 */
public class Singleton {
    // 인스턴스의 명시적 초기화
    private static Singleton instance = null;
 
    // 외부에서 인스턴스를 생성 못하도록 접근제한
    private Singleton() {
    }
 
    // synchronized 로 Lock 을 건다.
    // 이 메소드는 오직 한 스레드 씩 접근 가능
    public static synchronized Singleton GetInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
 
        return instance;
    }
}
cs


메소드에 Lock 을 걸었기 때문에 Thread-safe 함은 보장합니다. 

하지만 매번 사용할 때마다 한 개의 Thread 만 접근 가능하다는 것은 조금 불편한 진실인 듯 합니다. ㅡㅡ^


그렇기 때문에 정말 Thread-safe 한 부분만 Lock 이 걸리도록 변경해보려 합니다. Thread-safe 함이 보장되야할 부분은 instance 의 생성 부분입니다.


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
/**
 * 스레드의 Lock 을 이용한 싱글톤 예제 (2)
 *
 * Created by Doohyun on 2017. 5. 14..
 */
public class Singleton {
    // 스레드 간 최산 값을 읽어주기 위해 volatile 을 이용한 가시성 변수 사용
    private static volatile Singleton instance = null;
 
    // 외부에서 인스턴스를 생성 못하도록 접근제한
    private Singleton() {
    }
    
    // 개선된 Lock 처리
    public static Singleton GetInstance() {
        if (instance == null) {
            // instance 의 생성 부분만을 Lock 처리
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
 
        return instance;
    }
}
cs


하지만 이 방식은 가시성 변수를 사용하고 있습니다. 

instance 변수는 모든 스레드가 최신 값을 읽게 하기 위해 CPU 의 캐시를 사용하지 않으며, 그렇기 때문에 무조건 메모리로부터 데이터를 불러오게 됩니다.


- 간략한 운영체제 속 내용 소개 ㅡㅡ^


데이터는 모두 메모리(Heap, Data, Stack 영역) 위에 저장되어 있고, 보통은 CPU가 계산을 하기 위해 데이터를 읽어온 뒤 캐싱 작업을 합니다. 

캐싱작업을 하는 이유는 CPU 에서 미리 데이터를 저장함으로써, 보다 빠르게 읽고 계산하기 위해서죠.


하지만 여러 스레드가 변수를 동시접근을 하여 데이터를 조작하면, CPU 캐시의 값이 최신 값임을 보장할 수 없게 됩니다. 

즉 변경된 값을 못 읽어올 수 있죠.


그렇기 때문에 변수에 volatile 키워드를 붙여 가시성 변수임을 선언하며, 그 변수는 항상 메모리에서 읽어오게 됩니다. 

하지만 CPU 의 캐시에서 읽어오는 것보다 비용이 언제나 크겠죠? ㅜㅡㅜ



3. Lock 을 사용하지 않은 Lazy-bindig 방식의 Singleton


MARYLAND 대학의 연구원인 Bill puge 는 Lock 을 사용하는 방식은 단점을 해결하기 위해 새로운 Singleton 생성방식을 고안했습니다.


그 방식의 이름은 "Initialization on demand holder idiom" 으로 내부 클래스를 이용한 Holder 를 사용하는 기법은 클래스 로더 규칙에 의해 Lazy-binding 을 보장합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Initialization on demand holder idiom 방식의 싱글톤 생성
 *
 * Created by Doohyun on 2017. 5. 14..
 */
public class Singleton {
    // Instance 를 감싸는 홀더 생성. 클래스로더 규칙에 의해, ManagerHolder 가 로드될 때 Instance 생성
    private static final class ManagerHolder {
        private static final Singleton Instance = new Singleton();
    }
 
    // 외부에서 인스턴스를 생성 못하도록 접근제한
    private Singleton() {
    }
 
    // 개선된 Lock 처리
    public static Singleton GetInstance() {
        return ManagerHolder.Instance;
    }
}
cs


내부에 있는 ManagerHolder 는 Private 으로 접근제한 되어 있으며, 오직 GetInstance 를 통해서만 사용할 수 있습니다. 즉 GetInstance 가 호출 될 때 ManagerHolder 클래스가 로드되며 그 순간 명시적 초기화를 하고있는 내부의 Instance 가 생성됩니다.



4. Enum 을 사용하는 Singleton


Enum(열거형) 을 통해서도 Singleton 을 만들 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * ENUM 을 이용한 싱글톤
 *
 * Created by Doohyun on 2017. 5. 14..
 */
public enum Singleton {
    INTANCE;
    
    public void doSometing(){}
}
 
// Enum 싱글톤 사용법
Singleton.INTANCE.doSometing();
cs


간결한 사용법 때문에 최근 권장하는 스타일입니다. 


이 방식은 열거형 특성 상, 컴파일 시점에 미리 Instance 가 생성되기 때문에 Thread-safe 함을 보장합니다. 하지만 무조건 Eager-binding 입니다.


Singleton 을 만드는 여러 방식을 알아보았습니다. 우리는 Spring 이 아니더라도 Singleton 을 직접만들어 사용할 수 있게 되었습니다. 


하지만 주의할 것은 스터디에서도 살펴본 것 처럼 싱글톤 병에는 안 걸리게 조심합시다. ㅡㅡ^

반응형
Posted by N'

4. 실무 활용 패턴 (중).pdf



이어지는 패턴이야기입니다.


프린트해오면 최고, 사실 굳이 안해와도 되지 않을까? ~~ @.@ ~~


참고자료 

- 한국기술교육대학교 - 객체지향개발론및실습 (김상진 교수님)

- [Head First] Design Pattern

반응형
Posted by N'