본문 바로가기

Development/Spring Framework

스프링디자인패턴(4) 싱글턴 패턴 - Singleton Pattern

싱글턴 패턴: Singleton Pattern

 

Single에 주목해 번역하자면, 인스턴스를 하나만 만들어 사용하기 위한 패턴입니다. 즉, 하나의 인스턴스만 생성한 뒤 재사용하는 것인데요. 여러 인스턴스를 생성하면 자원 낭비가 발생해 예기치 못한 결과가 발생하는 경우를 예방하기 위해 사용됩니다. 예를 들어, 커넥션 풀, 디바이스 설정 객체 등이 존재하지요.

 

다시 한 번 더 이야기하자면, 싱글턴 패턴을 적용할 경우 의미상 두 개의 객체가 존재할 수 없으며, 필요한 요소는 다음과 같습니다.

 

  • 생성자 제약 : new를 생성할 수 없도록 생성자에 private접근자를 지정합니다.

  • 유일한 단일 객체 반환을 위해 정적 메서드(static method)가 필요합니다.
    직접적으로 생성자를 통해 인스턴스를 생성하지 않고, 객체를 반환하는 정적 메서드를 이용합니다.

  • 유일한 단일 객체를 참조할 정적(static) 참조 변수가 필요합니다.

간단한 코드로 보겠습니다.

 

 

  • 객체 참조 변수를 정적 참조 변수로 선언했습니다.

  • 해당 객체 참조 변수의 인스턴스를 생성하기 위해서는 생성자를 반환하는 정적 메서드가 사용됩니다.
    이는 if문을 이용해 정적 참조 변수의 인스턴스가 생성되지 않았을 경우 생성하여 정적 참조 변수의 메모리 주소를 반환합니다.

  • 다른 클래스에서 해당 인스턴스를 생성할 수 없도록 생성자의 접근 지정자를 private로 하여, 유일한 단일 객체로 존재하도록 합니다.

 

T 메모리를 생각해보겠습니다. static으로 선언된 메서드, 변수들은 static 영역에 생성됩니다. 그리고 만약 정적 참조 객체 변수가 생성된다면, Heap 영역에 메모리가 생성되며, 전체 코드에서 단 하나의 인스턴스만 된다면 재활용할 수 있습니다.

 


 

 

이러한 단일 객체는 결국 공유 객체로 이용되어, 속성을 갖지 않는 것이 정석입니다. 단순하게 생각하면 일관적이어야 하기 때문입니다. 만약 단일 객체가 속성을 갖게 되면, 하나의 참조 변수가 변경한 단일 객체 속성이 다른 참조 변수에 영향을 미치기 때문이지요.

 

하지만 예외도 존재합니다. 싱글턴 패턴을 갖는 단일 정적 참조 변수 메서드의 인자로 정적 참조 변수(즉, 단일 객체)를 가지는 것은 동일한 결과를 갖기에 가능합니다.

 

싱글턴 패턴은 유일한 객체를 반환하기 위한 정적 메서드에 생성 패턴이 적용되어 있습니다.

생성 패턴

객체 생성에 관여되는 패턴으로, 객체의 생성과 조합을 캡슐화하여 특정 객체가 생성되거나 변경되어도 프로그램 구조에 크게 영향을 받지 않도록
유연성을 제공합니다.

 


 

예제를 하나 만들어보겠습니다.

한 카페가 있습니다. 카페의 매니저는 단 한 명입니다. (여러 명이 있으면 아르바이트생 관리와 운영이 혼란스럽거든요.) 아르바이트생들과 면담이 있는 날인데요. 매니저는 면담 내용을 기록한 뒤 조회합니다.

 

  • 카페에 매니저는 단 한 명 존재합니다. 그렇기에 매니저 클래스를 싱글턴으로 구현합니다.

 

  • 아르바이트생들은 매니저와 면담을 할 수 있습니다.(Interviewable) 매니저는 면담이 진행되지 않을 경우 관리 페이지에서 'no interview'를 확인합니다.

 

 

아직 설계능력이 부족해 살짝 꼬인 것 같습니다.. (피드백이 필요할거 같네용..) 싱글턴 사용에만 집중해서 우선 봐주시면 감사드리겠습니다.

이처럼 싱글턴 패턴은 단 하나의 인스턴스만 요할때 사용됩니다.

 

 

싱글턴 패턴은 다른 문제가 발생하지 않나요?

 

다중 스레드에서 인스턴스가 한 개 이상 생성되는 문제가 발생할 수 있습니다. 즉, 경합 조건이 발생할 수 있기 때문인데요. 여기서 경합 조건(Race Condition) 이란, 메모리와 같은 동일한 자원을 두 개 이상의 스레드가 사용하려고 경합하는 현상입니다.

 

아래와 같이 상황을 가정해보겠습니다.

  • Manager 인스턴스가 생성되지 않았을 때, 스레드 A는 getManager( )의 if문으로 생성 여부를 확인합니다. 이때, 아직 생성되지 않았기에 정적 객체 참조 변수의 값은 null 입니다.

  • 스레드 A가 생성자를 호출하기 전, 스레드 B가 if문을 실행해 생성 여부를 확인한다면 어떨까요? 이때 아직 호출 전이기에 마찬가지로 정적 객체 참조 변수의 값은 null 입니다.

  • 이대로 스레드 A와 스레드 B가 실행된다면, Manager 클래스는 두 개가 생성되는 불상사가 발생합니다. ( 아이고야 ~ )

그렇다면 어떻게 해결하면 좋을까요?

두 가지가 존재합니다. [ (1) 정적 객체 참조 변수에 바로 인스턴스를 생성하는 방법   (2) 인스턴스를 생성하는 메서드를 동기화하는 방법 ] 입니다.

 

코드로 살펴볼까요?

(1) 정적 객체 참조 변수에 바로 인스턴스 생성

 

  • 객체 생성 전 클래스가 메모리에 로딩될 때 생성되어 초기화가 한 번만 실행됩니다.

  • 프로그램 시작부터 종료까지 메모리에서 없어지지 않아, 모든 코드에서 접근할 수 있습니다.

 

(2) 인스턴스를 생성하는 메서드 동기화

 

 

  • 인스턴스를 생성하는 메서드를 임계 구역으로 변경합니다. 이로, 다중 스레드 환경에서 동시에 여러 스레드가 getManager( ) 메서드를 소유하는 객체에 접근하는 것을 방지할 수 있습니다!

  • 위 코드에서는 해당되지 않아 존재하지 않지만, 공유 변수에 접근하는 부분을 임계 구역으로 변경해야 합니다.
    이 방법은 getInstance( )를 잠궈(일명 Lock을 걸기에) 속도가 느리다는 단점이 있습니다.

 

또한 싱글턴 패턴을 구현하기 위해서는 Enum을 이용해 구현하는 것이 바람직하다고 하는데요.

이 부분에 대해서는 다음 포스팅으로 추가하겠습니다.