- 인스턴스가 오직 1개만 생성되어야 하는 경우에 사용하는 패턴이다.
- 하나의 인스턴스를 메모리에 등록해서 여러 스레드가 동시에 공유하여 사용하므로 요청이 많은 곳에서 효율을 높일 수 있다.
- getInstance 메서드를 통해 모든 클라이언트에게 동일한 인스턴스를 반환하는 작업을 수행한다.
/*
Printer 생성자를 외부에서 사용 불하도록 private으로 선언
자기자신에 대한 인스턴스를 하나 만들어 외부에 제공해 줄 메서드가 필요하기때문에
static메서드 / static 변수를 사용
(클래스의 인스턴스를 통하지 않고서도 메서드를 실행할 수 있고 변수를 참조할 수 있다)
만약 new Printer()가 호출 되기 전이면 인스턴스 메서드인 print()메서드는 호출불가
*/
public class Printer {
// 외부에 제공할 자기 자신의 인스턴스
private static Printer printer = null;
private Printer() { }
// 자기 자신의 인스턴스를 외부에 제공
public static Printer getPrinter(){
if (printer == null) {
// Printer 인스턴스 생성
printer = new Printer();
}
return printer;
}
public void print(String str) {
System.out.println(str);
}
}
// Client에서의 사용 코드
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 Client {
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 인스턴스 생성
user[i] = new User((i+1))
user[i].print();
}
}
}
다중 스레드에서 Printer 클래스를 이용할 때 인스턴스가 1개 이상 생성되는 경우가 발생할 수 있다.
이를 해결하기 위해선 다음과 같은 방법이 있다.
-
정적 변수에 인스턴스를 만들어 바로 초기화 하는 방법
-
인스턴스를 만드는 메서드에 동기화하는 방법
-
정적 변수에 인스턴스를 만들어 바로 초기화 하는 방법
public class Printer {
// 기존 위의 싱글톤 방식
private static Printer printer = null; // --> X
// static 변수에 외부에 제공할 자기 자신의 인스턴스를 만들어 초기화
private static Printer printer = new Printer(); // --> O
private Printer() { }
// 자기 자신의 인스턴스를 외부에 제공
public static Printer getPrinter(){
return printer;
}
public void print(String str) {
System.out.println(str);
}
}
- 인스턴스를 만드는 메서드에 동기화하는 방법
public class Printer {
// 외부에 제공할 자기 자신의 인스턴스
private static Printer printer = null;
private int counter = 0;
private Printer() { }
// 인스턴스를 만드는 메서드 동기화 (임계 구역, synchronized)
public synchronized static Printer getPrinter(){
if (printer == null) {
printer = new Printer(); // Printer 인스턴스 생성
}
return printer;
}
public void print(String str) {
// 오직 하나의 스레드만 접근을 허용함 (임계 구역)
// 성능을 위해 필요한 부분만을 임계 구역으로 설정한다.
synchronized(this) {
counter++;
System.out.println(str + counter);
}
}
}
♠ synchronized 사용할 때
- 멀티 스레드로 인해 동기화를 제어해야하는 경우
- 멀티 스레드로 동시접근되는 것 을 막는 것이 주 목적
- synchronized 사용법
-
싱글톤 효과와 동일한 효과를 얻을 수 있다
-
객체를 전혀 생성하지 않고 메서드를 사용할 수 있다.
-
일반적으로 실행할 때 바인딩되는 인스턴스 메서드를 사용하는 것 보다 성능이 우수하다.
-
인터페이스를 구현해야하는 경우에는 정적 클래스를 사용할 수 없다.
-
public class Printer { private static int counter = 0; // 메서드 동기화 (임계 구역) public synchronized static void print(String str) { counter++; System.out.println(str + counter); } } public class UserThread extends Thread{ // 스레드 생성 public UserThread(String name) { super(name); } // 현재 스레드 이름 출력 public void run() { Printer.print(Thread.currentThread().getName()); } } public class Client { private static final int THREAD_NUM = 5; public static void main(String[] args) { UserThread[] user = new UserThread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { // UserThread 인스턴스 생성 user[i] = new UserThread((i+1)); user[i].start(); } } }
-
Thread-safety와 Serialization이 보장된다.
-
Reflection을 통한 공격에도 안전하다. (Reflection, 클래스의 구조확인과 값을 가져오거나 메서드 호출할때 사용)
-
따라서 Enum클래스를 이용해서 싱글톤 패턴을 구현하는 것이 가장 좋은 방법이다.
-
public enum SingletonTest { INSTANCE; public static SingletonTest getInstance() { return INSTANCE; } }
- https://gmlwjd9405.github.io/2018/07/06/singleton-pattern.html
- https://medium.com/webeveloper/%EC%8B%B1%EA%B8%80%ED%84%B4-%ED%8C%A8%ED%84%B4-singleton-pattern-db75ed29c36
- https://jeong-pro.tistory.com/86
싱글톤 패턴 : 애플리케이션이 시작될 때 어떤 클래스가 최초 한번만 메모리를 할당하고 static 메모리에 인스턴스를 만들어 사용하는 디자인패턴