JUINTINATION
싱글턴(Singleton) 패턴 본문
반응형
싱글턴 패턴이란?
'단 하나의 원소만을 가진 집합'이라는 수학 이론에서 유래된 인스턴스가 딱 하나만 생성되고 어디에서든 이 인스턴스에 접근할 수 있도록 보장하는 패턴이다.
클래스에 싱글턴 패턴을 적용하는 방법은 다음과 같다.
- 생성자를 private 메서드로 변경한다.
class Singleton {
private Singleton() {}
}
- 접근할 하나의 인스턴스를 static 인스턴스로 만들어준다.
class Singleton {
private static Singleton instance = null;
private Singleton() {}
}
- 하나의 static 인스턴스에 접근하기 위한 static 메서드를 만든다.
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
return instance == null ? new Singleton() : instance;
}
}
예제 코드를 만들어 확인해보면 결과는 다음과 같다.
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("User" + i);
user[i].print();
}
}
}
class User {
private String name;
public User(String name) {
this.name = name;
}
public void print() {
Printer printer = Printer.getPrinter();
printer.print(this.name + " is using " + printer.toString());
}
}
class Printer {
private static Printer printer = null;
private Printer() {}
public static Printer getPrinter() {
if (printer == null) {
printer = new Printer();
}
return printer;
}
public void print(String str) {
System.out.println(str);
}
}
User0 is using Printer@19dfb72a
User1 is using Printer@19dfb72a
User2 is using Printer@19dfb72a
User3 is using Printer@19dfb72a
User4 is using Printer@19dfb72a
- 위 코드의 문제점
- 다중 스레드에서 Printer 클래스의 인스턴스가 1개 이상 생성될 가능성이 존재한다.
- Printer 클래스의 인스턴스가 아직 생성되지 않았을 때 스레드 1이 getPrinter 메서드의 if문을 실행해 인스턴스가 생성되어있는 상태인지 확인한다. 현재 printer 변수는 null인 상태이다.
- 만약 스레드 1이 생성자를 호출해 인스턴스를 만들기 전 스레드 2가 if문을 실행해 인스턴스가 생성되어있는 상태인지 확인한다. 현재 printer 변수는 null인 상태이므로 생성자를 호출하는 코드를 실행한다.
- 스레드 1도 스레드 2와 마찬가지로 인스턴스를 생성하는 코드를 실행하게 되면 결과적으로 Printer 클래스의 인스턴스가 2개 생성된다.
- 다중 스레드에서 Printer 클래스의 인스턴스가 1개 이상 생성될 가능성이 존재한다.
public class Main {
private static final int USER_NUM = 5;
public static void main(String[] args) {
UserThread[] user = new UserThread[USER_NUM];
for (int i = 0; i < USER_NUM; i++) {
user[i] = new UserThread("User" + i);
user[i].start();
}
}
}
class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
public void run() {
Printer printer = Printer.getPrinter();
printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
}
}
class Printer {
private static Printer printer = null;
private Printer() {}
public static Printer getPrinter() {
if (printer == null) {
// 의도적 지연
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
printer = new Printer();
}
return printer;
}
public void print(String str) {
System.out.println(str);
}
}
User2 is using Printer@1133ea76
User0 is using Printer@7b6ade42
User3 is using Printer@506d0b61
User1 is using Printer@506d0b61
User4 is using Printer@35dd8ea8
이 때 다음과 같이 프린터 인스턴스에 유지해야 하는 값이 있다면 문제가 발생할 수 있다.
public class Main {
private static final int USER_NUM = 5;
public static void main(String[] args) {
UserThread[] user = new UserThread[USER_NUM];
for (int i = 0; i < USER_NUM; i++) {
user[i] = new UserThread("User" + i);
user[i].start();
}
}
}
class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
public void run() {
Printer printer = Printer.getPrinter();
printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
}
}
class Printer {
private static Printer printer = null;
private int cnt = 0;
private Printer() {}
public static Printer getPrinter() {
if (printer == null) {
// 의도적 지연
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
printer = new Printer();
}
return printer;
}
public void print(String str) {
System.out.println(str + " and cnt is " + (++cnt));
}
}
User3 is using Printer@35dd8ea8 and cnt is 1
User4 is using Printer@5463113d and cnt is 1
User1 is using Printer@6a0fc530 and cnt is 1
User0 is using Printer@1ff40620 and cnt is 1
User2 is using Printer@4d0b6d80 and cnt is 1
이를 해결하기 위해 다음과 같은 방법을 사용할 수 있다.
- 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법
- 인스턴스를 만드는 메서드에 동기화하는 방법
방법 1
public class Main {
private static final int USER_NUM = 5;
public static void main(String[] args) {
UserThread[] user = new UserThread[USER_NUM];
for (int i = 0; i < USER_NUM; i++) {
user[i] = new UserThread("User" + i);
user[i].start();
}
}
}
class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
public void run() {
Printer printer = Printer.getPrinter();
printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
}
}
class Printer {
private static Printer printer = new Printer();
private int cnt = 0;
private Printer() {}
public static Printer getPrinter() {
return printer;
}
public void print(String str) {
System.out.println(str + " and cnt is " + (++cnt));
}
}
User2 is using Printer@5eaaef1c and cnt is 5
User4 is using Printer@5eaaef1c and cnt is 4
User3 is using Printer@5eaaef1c and cnt is 3
User0 is using Printer@5eaaef1c and cnt is 1
User1 is using Printer@5eaaef1c and cnt is 2
방법 2
public class Main {
private static final int USER_NUM = 5;
public static void main(String[] args) {
UserThread[] user = new UserThread[USER_NUM];
for (int i = 0; i < USER_NUM; i++) {
user[i] = new UserThread("User" + i);
user[i].start();
}
}
}
class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
public void run() {
Printer printer = Printer.getPrinter();
printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
}
}
class Printer {
private static Printer printer = null;
private int cnt = 0;
private Printer() {}
public synchronized static Printer getPrinter() {
if (printer == null) {
printer = new Printer();
}
return printer;
}
public void print(String str) {
System.out.println(str + " and cnt is " + (++cnt));
}
}
User2 is using Printer@5eaaef1c and cnt is 4
User0 is using Printer@5eaaef1c and cnt is 5
User1 is using Printer@5eaaef1c and cnt is 1
User4 is using Printer@5eaaef1c and cnt is 2
User3 is using Printer@5eaaef1c and cnt is 3
cnt 변수 또한 동기화를 해주면 순서대로 1, 2, 3, 4, 5를 출력할 수 있다.
public class Main {
private static final int USER_NUM = 5;
public static void main(String[] args) {
UserThread[] user = new UserThread[USER_NUM];
for (int i = 0; i < USER_NUM; i++) {
user[i] = new UserThread("User" + i);
user[i].start();
}
}
}
class UserThread extends Thread {
public UserThread(String name) {
super(name);
}
public void run() {
Printer printer = Printer.getPrinter();
printer.print(Thread.currentThread().getName() + " is using " + printer.toString());
}
}
class Printer {
private static Printer printer = null;
private int cnt = 0;
private Printer() {}
public synchronized static Printer getPrinter() {
if (printer == null) {
printer = new Printer();
}
return printer;
}
public void print(String str) {
synchronized (this) {
System.out.println(str + " and cnt is " + (++cnt));
}
}
}
User2 is using Printer@3bf8335e and cnt is 1
User3 is using Printer@3bf8335e and cnt is 2
User0 is using Printer@3bf8335e and cnt is 3
User1 is using Printer@3bf8335e and cnt is 4
User4 is using Printer@3bf8335e and cnt is 5
싱글턴 패턴 컬레보레이션
싱글턴 패턴을 적용하기 위한 컬레보레이션이다.
생성자는 private로,
instance 객체는 private static으로,
getInstance 메서드는 public static으로 선언한다.
728x90
'JAVA객체지향디자인패턴' 카테고리의 다른 글
스테이트(State) 패턴 (0) | 2023.07.06 |
---|---|
스트래티지(Strategy) 패턴 (0) | 2023.07.06 |
디자인패턴 개요 (0) | 2023.07.03 |
좋은 객체 지향 설계의 5가지 원칙 (SOLID) (0) | 2023.07.02 |
객체 지향 원리 (0) | 2023.07.02 |
Comments