Singleton

Singleton은 유일한 Instance를 만들어 사용하는 방법이다.

생성자를 private으로 하여 다른 곳에서 Instance를 직접 생성할 수 없으며, public getInstance()를 통해서만 instance를 가져가서 사용할 수 있다.

  • 객체 = Object = Compile된 각각의 .class file
  • 개체 = Instance = Compile된 .class file을 new 연산자를 사용하여 Memory에 Loading한 상태

기본적으로는 이런 느낌이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Iamsingle {
private static Iamsingle mInstance;
private Iamsingle() {
}
public static Iamsingle getInstance() {
if (mInstance == null) {
mInstance = new Iamsingle(); // 누군가가 만들어둔 Instance가 없다면 만들어서 돌려줍시다.
}
return mInstance;
}
}

필요성

설정 파일 등 여러 개가 있으면 혼란스러울 수 있는 경우 Singleton을 통해 하나의 자원만 사용할 수 있게 한다.

전역적으로 접근이 가능해야 하지만 단 하나만 있어야 한다.

전역 변수는 여러 개가 생성될 수 있기 때문에 Singleton을 사용하는 것이다.

싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴입니다. - Head First Design Patterns

또한, Global Class는 어플리케이션이 시작될 때 항상 로딩되지만 Singleton Class는 시작되는 지점을 개발자가 지정할 수 있다.

Trouble shooting

MultiThreading 문제 해결

위쪽에서 표현한 코드는 MultiThread 환경에서 추가적인 Instance의 생성을 완전히 방어할 수 없다.

고로, Thread 사용 시 기본적인 방어 방법인 Syncronized를 붙여보기로 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Iamsingle {
private static Iamsingle mInstance;
private Iamsingle() {
}
public static synchronized Iamsingle getInstance() {
if (mInstance == null) {
mInstance = new Iamsingle(); // 누군가가 만들어둔 Instance가 없다면 만들어서 돌려줍시다.
}
return mInstance;
}
}

Synchronized로 인한 성능 문제 해결

Synchronized로 인한 MultiThreading 시의 성능은 엄청나게 떨어진다.

고로, Instance를 미리 생성해 보도록 한다.

1
2
3
4
5
6
7
8
9
10
11
public class Iamsingle {
private static Iamsingle mInstance = new Iamsingle();
private Iamsingle() {
}
public static synchronized Iamsingle getInstance() {
return mInstance;
}
}

문제를 해결하는 또 다른 방법

DCL (Double-Checking Locking) 이라는 방법으로 Instance가 생성되었으면 그냥 넘기고, 없는 경우에만 synchronized 를 걸어주어 Instance를 요청하는 Thread들에 부담을 덜어주는 방법이다.

volatile keyword는 Java 코드의 변수(Variable)를 메모리에 저장(Store) 하라는 의미이다. volatile 변수는 CPU cache를 사용하지 않고 Main memory에 직접 read/write 한다.

volatile에 대한 자세한 내용은 여기를 참조해 보자.

결론적으로 여기서는 CPU Cache를 통할 때 변수의 무결성을 보장할 수 없으므로 volatile을 통해 Main memory에 직접 접근하겠다는 뜻이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Iamsingle {
private volatile static Iamsingle mInstance;
private Iamsingle() {
}
public static Iamsingle getInstance() {
if (mInstance == null) {
// 여기까지는 MultiThread 방어가 되지 않은 상태.
synchronized(this) {
if (mInstance == null) { // 위의 첫 번째 null check를 통과한 thread가 대기하고 있다가 들어왔을 확률이 있기 때문에 한 번 더 check.
mInstance = new Iamsingle();
}
}
}
return mInstance;
}
}

마무리

  • 무분별하게 리팩토링 하겠다고 Singleton 남발하지 말 것.
  • 전역 설정이 필요할 때 등 꼭 필요할 때만 사용할 것.
  • 위에서 나열한 방법들 중 해당 어플리케이션에 적절한 것을 사용할 것. 단일 Thread를 사용하는 프로그램에서 굳이 synchronized로 성능 저하를 시킬 필요는 없다.

출처

Share