본문 바로가기

소프트웨어/디자인패턴

[소프트웨어/디자인패턴] 싱글톤 패턴(Singleton Pattern)

싱글톤 패턴은 한정된 자원을 효율적으로 쓸 수 있도록 만들어준다.

Context

어떤 자원은 한정적인데 여러곳에서 또는 여러명이 사용해야하는 경우가 있다. 만약에 엘레베이터, 프린터, 정수기 등등이 될 수 있다. 이들은 필요할 때마다 생성하여 사용하기 어려운 사물들이다. 자 그럼 제한된 자원을 공유하여 사용해야하는데 이런경우 어떻게 해야할 수 있을까?

방향

  • 생성자는 외부에 공개하지 않는다.
  • private static의 자기 자신의 인스턴스를 가진다.
  • public static getInstance():SelfClass로 그 인스턴스가 null일 때는 생성해주고 아닌경우에는 그냥 해당 인스턴스를 반환한다.

위 방향을 모두 구현하면 아래 코드와 같다.

class LimitedResource {
    private static LimitedResource instance = null;

    private LimitedResource() { }

    public static LimitedResource getInstance () {
        if(instance == null) {
            instance = new LimitedResource();
        }

        return instance;
    }
}

Problem

사실 멀티쓰레드 환경이 아닌 경우에는 위 코드로 문제가 발생하지 않지만, 2개 이상의 스레드 환경에서는 문제가 발생할 수 있다. 이유로는 동시에 2개 이상의 스레드가 getInstance()를 호출했을 경우 단 하나의 인스턴스만을 반환한다고 보장할 수 없기 때문이다.

  1. 동시의 2개 이상의 스레드가 getInstance()를 호출한다.
  2. 2개 이상의 스레드가 동시에 instance == null문을 실행하여 모두 true를 반환한다.
  3. 서로 다른 인스턴스 객체를 생성해 반환한다.
public class RegacyResource {
    private static RegacyResource instance = null;

    private RegacyResource() { }

    public static RegacyResource getInstance () {
        if(instance == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new RegacyResource();
        }

        return instance;
    }
}

try를 쓴 이유는 해당 경우를 효과적으로 재현하기 위해서다, 사실 Tread.sleep(1)를 하지 않아도 필자의 컴퓨터에서는 2개 스레드의 경우 다른 인스턴스를 반환한다.

위 코드의 경우 아래 테스트를 돌리면 거의 대부분 테스트가 통과할 것이다.

public class RegacyResourceTest {

    @Test
    public void getInstanceInMultiThread() throws Exception {

        RegacyResource[] resources = new RegacyResource[2];

        new Thread(() -> {
            resources[0] = RegacyResource.getInstance();
        }).start();
        new Thread(() -> {
            resources[1] = RegacyResource.getInstance();
        }).start();

        Thread.sleep(10);

        assertNotEquals(resources[0], resources[1]);
    }
}

Solution

멀티스레드 환경에서의 해결 방법은 아래 2가지가 존재한다.

  • synchronized키워드를 사용하여 메서드를 thread-safe하게 만든다.
public class SafetyResource {
    private static SafetyResource instance = null;

    private SafetyResource() { }

    public synchronized static SafetyResource getInstance () {
        if(instance == null) {
            instance = new SafetyResource();
        }

        return instance;
    }
}
  • static변수를 바로 초기화하여 반환만 한다.
class SafetyResource2 {
    private static SafetyResource2 instance = new SafetyResource2();

    private SafetyResource2() { }

    public static SafetyResource2 getInstance() {
        return instance;
    }
}

전자의 경우 많은 참조를 요구하면 한번에 한 스레드밖에 처리하지 못하므로 효율적이지 못하고, 아래의 경우 실제 사용하지 않는 경우가 많은 경우 효율적이지 못하다. 적절하게 사용하면 될 듯하다.



< 싱글턴 패턴의 모델이다>


Summary

싱글턴 패턴은 인스턴스가 오직 하나만 생성되는 것을 보장하고 어디에서든 이 인스턴스에 접근할 수 있도록 하는 디자인 패턴이다.