싱글톤 패턴(Singleton Pattern)
- Singleton 패턴은 인스턴스를 불필요하게 생성하지 않고 오직 JVM내에서 한 개의 인스턴스만 생성하여 재사용을 위해 사용되는 디자인패턴
프린터 관리자 Example
- 단 하나의 프린터 객체를 만들어 사용자 객체가 사용하려 한다.
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
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void print() {
Printer printer = printer.getPrinter();
printer.print(this.name + " print using " + printer.toString() + ".");
}
}
public class Printer {
private static Printer printer = null;
private Printer() { }
public static Printer getPrinter() {
if (printer = = null) {
printer = new Printer(); // Printer 인스턴스 생성
}
return printer;
}
public void print(String str) {
System.out.println(str);
}
}
public class Main {
private static final int User_NUM = 5;
public static void main(String[] args) {
User[] user = new User[User_NUM];
for (int i = 0; i < User_NUM; i++) {
user[i] = new User((i + 1) + "-user"); // User 인스턴스 생성
user[i].print();
}
}
}
- 실행 결과
문제점
- printer 인스턴스가 아직 생성되지 않았을 때 스레드 1이 getPrinter메서드의 if문을 실행해 이미 인스턴스가 생성되었는지 확인한다. 현재 printer 변수는 null인 상태다. 만약 스레드 1이 생성자를 호출해 인스턴스를 만들기 전 스레드 2가 if문을 실행해 printer 변수가 null인지 확인한다. 현재는 null이므로 인스턴스를 생성하는 코드, 즉 생성자를 호출하는 코드를 실행하게 된다. 스레드1도 스레드2와 마찬가지로 인스턴스를 생성하는 코드를 실행하게 되면 결과적으로 Printer클래스의 인스턴스가 2개 생성된다.
- => 다중 스레드 문제로 인한 동기화 처리가 요구된다.
해결책
- 1) 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법(비추천)
- 클래스가 참조 될 때 메모리(JVM)에 로딩되어 생성됨 , instance변수가 굳이 바인딩 될 필요가 없어도 클래스를 참조하는 순간, 불필요하게 객체가 생성됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Printer {
private static Printer instance = new Printer();
private Printer() {
}
public static Printer getPrinter() { // synchronized 임계구역설정
return instance;
}
public void print(String str) {
System.out.println(str);
}
}
- 2) 인스턴스를 만드는 메서드에 동기화하는 방법(비추천)
- synchronized getinstance()의 경우 인스턴스를 리턴 받을 때마다 Thread동기화 때문에 불필요하게 lock이 걸리게 되어 비용 낭비가 크다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Printer {
private static Printer instance = null;
private Printer() {
}
public synchronized static Printer getPrinter() { // synchronized 임계구역설정
if(instance == null) {
instance = new Printer();
}
return instance;
}
public void print(String str) {
System.out.println(str);
}
}
- 3) LazyHolder 기법(가장 이상적인 방법)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Printer {
private Printer() {
}
public static class LazyHolder {
private static Printer instance = new Printer();
} // getPrinter가 호출되는 순간 이 클래스가 메모리에 적재되어 instance 변수가 생성됨
public static Printer getPrinter() { // synchronized 임계구역설정
return LazyHolder.instance;
}
public void print(String str) {
System.out.println(str);
}
}
출처
- Java객체지향 디자인패턴(한빛미디어)
- https://javaplant.tistory.com/21