본문 바로가기
Design Pattern

1. 싱글톤 패턴 (Singleton Pattern)

by 킹차니 2021. 10. 30.

 

 

 

 

 

 

싱글톤 패턴은 하나의 인스턴스만을 생성하는 패턴이다. 

일번적인 객체들은 Class를 정의했다면 new를 사용하여 얼마든지 수많은 객체를 생성해낼 수 있다. 

하지만 싱글톤 패턴은 인스턴스를 하나만 생성하도록 하는 것이다.

 

게임 설정을 예로 들어보자. 게임을 시작하기 전에 Setting에 들어가서 소리를 높이거나 줄이거나 키를 변경하거나 할 수 있다.

또한 게임이 시작되고 나서도 Setting에 들어가서 소리를 높이거나 줄이는 등의 여러가지 설정을 다룰 수 있어야 한다.

게임을 하기 전에 volume을 30으로 맞춰두었는데, 게임을 시작하니까 volume이 30이 아니여서 다시 설정해야한다면?

이는 매우 불편할 것이다. 하여 Setting을 위한 인스턴스는 하나만 생성되어 어플리케이션 내에서 공유되도록 하고, 게임 시작 전이나 게임 시작 후에나 같은 상태를 유지해야 할 것이다.

 

먼저 싱글톤이 아닌 경우를 코드로 보자.

Setting클래스

 

마음껏 객체를 생성할 수 있다. 이때는 모든 인스턴스들이 서로 다른 인스턴스이다.

 

 

위와 같이 여러 인스턴스를 생성하는 것을 막기 위해서는 생성자를 private으로 감춘다.

private으로 바꾸면 Setting을 생성하는 코드에 컴파일 에러가 발생할 것이다.

생성자를 private으로 수정

 

컴파일 에러 발생

그렇다면 외부에서 같은 Setting 인스턴스를 가져올 수 있는 방법은 무엇일까?

아래와 같이 하면 된다.

 

방법 1: private 생성자에 static 메소드

싱글톤 패턴이 적용된 Settings

 

getInstance메소드로 Setting 인스턴스를 얻을 수 있다.

위와 같은 방법을 적용하면 settings1, settings2, settings3는 모두 같은 인스턴스일 것이다.

 

하지만

위와 같은 싱글톤 패턴은 싱글 스레드 환경에서는 문제가 되지 않지만 멀티 스레드 환경에서 문제가 된다.

두 개 이상의 스레드가 getInstance를 통해 Settings인스턴스를 얻으려고 경합하는 과정에서 서로 다른 인스턴스가 만들어질 수 있기 때문이다.

 

하여 멀티스레드 환경에서 safe한 몇 가지 방법을 알아보자.

 

 

 

방법 2:  synchronized 사용하기

위와 같은 코드는 sychronized를 사용하여 getInstance메소드에 하나의 스레드만 접근하도록 한다.

하지만 위와 같은 sychronized는 cost가 많이 드는 작업이다. 하여 아래와 같은 방법으로 cost를 줄일 수 있다.

 

방법 3: eager initialization (이른 초기화)

싱글톤 인스턴스를 미리 생성해두고 getInstance를 호출한 쪽에 생성해둔 인스턴스를 반환하는 것이다.

하지만 위의 방법은 어플리케이션이 실행되면 바로 인스턴스를 만들기 때문에, 해당 인스턴스를 사용하지 않는다면 메모리 낭비일 것이다.

하여 sychronized의 cost도 줄이면서, 사용 시점에 생성하는 방법이 있다.

 

방법 4: double checked locking

위의 방법은 두번의 검사를 한다. 하여 13라인의 if문에서 많은 스레드들이 걸러지고, 행여나 2개 이상의 스레드가 if문을 동시에 통과하더라도, 15라인의 synchronized에 의해 하나의 스레드만 통과할 수 있게 된다.

cost도 줄이고, 사용 시점에만 생성될 수 있어 좋지만 코드가 꽤나 복잡해진다. (volatile과 if문을 두번 사용하여 검사.)

하여 더 간단한 방법을 알아보자.

 

방법 5: static  inner class

위와 같은 방법은 멀티스레드 환경에서도 안전하고, getInstance를 호출한 시점에만 인스턴스가 생성되던지, return되던지 한다. 또한 위의 방법4처럼 복잡하지도 않다.

 

 

하지만 사용자는 아래와 같은 두가지 방법을 사용하여 얼마든지 싱글톤을 깨트릴 수  있다.

싱글톤을 깨트리는 방법 2가지를 보자.

 

싱글톤을 깨는 방법 1 : 리플랙션 사용하기

위와 같이 리플렉션을 사용하면 싱글톤이 깨져, settings와 settings1은 다른 인스턴스가 된다.

 

 

싱글톤을 깨는 방법 2 : 직렬화, 역직렬화 사용하기

우선 이 방법을 위해서는 Settings가 Serializable을 Implements해야 한다.

 

그리고 사용하는 부분은 아래와 같다.

 

 

이처럼 두가지 방법으로 싱글톤이 깨지는 것을 보았다. 하여 이를 막기 위해서는 싱글톤을 다른 방법으로 구현해야 한다.

 

 

 

방법6: Enum으로 만들기

먼저 아래와 같이 enum으로 만들면 리플렉션을 사용해도 싱글톤을 깰 수 없다. 또한 이는 직렬화 역직렬화에도 안정적이다.

하지는 이는 enum클래스 로딩 시점에 안의 INSTANCE가 바로 만들어진다는 단점이 있다.

또한 상속을 활용할 수 없다는 단점이 있다.

 

그래서 결론은 방법5( static inner class )를 권장한다.

 

 

그렇다면 싱글톤은 어디에서 쓰일까?

 

먼저 자바의 Runtime클래스는 싱글톤이 적용된 클래스이다.

new로 Runtime을 생성하려고 하면 컴파일 에러가 발생한다.

Runtime클래스의 내부는 아래와 같다.

 

 

 

 

출처: 인프런 백기선님 '코딩으로 학습하는 GoF 강의'
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4