싱글턴
객체를 하나만 만들 수 있는 클래스이다.
1. 정적 멤버를 final 로 선언한다. (private 생성자 , static 멤버)
private 생성자는 public static final 필드인 인스턴스를 초기화할 때 한 번만 호출되기 때문에 항상 같은 객체를 참조한다.
public class Singleton1{
public static final Elvis INSTANCE = new Singleton1();
private Singleton1(){...}
}
Singleton1 singleton1 = Singleton1.INSTANCE; // 항상 동일한 인스턴스 참조
Singleton1 singleton11 = Singleton1.INSTANCE; // 항상 동일한 인스턴스 참조
System.out.println(singleton1 == singleton11);
+ 선언만 보면 클래스가 싱글턴인지 금방 알 수 있어 좋다.
+ 정적 팩토리 메서드를 사용하는 방법에 비해 더 명확하고 간단하다.
- 최신 JVM 의 경우 정적 팩토리 메소드 호출을 거의 항상 인라인 처리해 버리기 때문에 성능이 굳이 더 좋지는 않다.
- 인스턴스를 사용하지 않아도 인스턴스를 호출하게 된다.
- 리플렉션 기능을 통해 private 생성자를 호출할 수 있다.
-> 생성자에 카운트 변수나 인스턴스의 존재 여부를 확인하여 인스턴스가 여러 개 생성되는 상황을 방지할 수 있다.
static int count;
private Singleton1() {
count ++;
if (count != 1) {
throw new IllegalStateException("this object should be singleton");
}
if(INSTANCE != null){
throw new RuntimeException("생성자를 호출할 수 없습니다!");
}
}
private Singleton1() {
if(INSTANCE != null){
throw new RuntimeException("생성자를 호출할 수 없습니다!");
}
}
- 두 방법 모두 직렬화한다면 역직렬화 할 때 같은 타입 인스턴스가 여러 개 생길 수 있다.
-> 인스턴스에 "transient" 키워드를 선언하고 기존에 있던 인스턴스를 반환하도록 readResolve() 을 작성한다.
private static final transient Singleton2 INSTANCE = new Singleton2();
private Object readResolve() {
return INSTANCE;
}
2. public 으로 선언된 정적 팩토리 메서드를 이용한다. (private 생성자 , static 멤버)
getInstance() 는 항상 같은 객체에 대한 참조를 반환한다.
public class Elvis{
private static final Elvis INSTANCE = new ELVIS();
private Elvis(){...}
public static Elvis getInstance() {return INSTANCE;}
}
Singleton2 singleton2 = Singleton2.getInstance();
Singleton2 singleton22 = Singleton2.getInstance();
System.out.println(singleton2 == singleton22);
+ api 를 변경하지 않고도 싱글턴 패턴을 포기할 수 있다.
+ 정적 팩토리의 메소드 참조를 공급자로 사용할 수 있다.
Supplier<Singleton2> s2supplier = Singleton2::getInstance;
s2supplier.get();
- 인스턴스를 사용하지 않아도 인스턴스를 호출하게 된다.
- 리플렉션 기능을 통해 private 생성자를 호출할 수 있다.
-> 생성자에 카운트 변수나 인스턴스의 존재 여부를 확인하여 인스턴스가 여러 개 생성되는 상황을 방지할 수 있다.
try {
Constructor<Singleton1> constructor = Singleton1.class.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
- 두 방법 모두 직렬화한다면 역직렬화 할 때 같은 타입 인스턴스가 여러 개 생길 수 있다.
-> 인스턴스에 "transient" 키워드를 선언하고 기존에 있던 인스턴스를 반환하도록 readResolve() 을 작성한다.
private static final transient Singleton2 INSTANCE = new Singleton2();
private Object readResolve() {
return INSTANCE;
}
3. 원소가 하나뿐인 enum 자료형 정의한다.
JDK 1.5 부터 가능하며 기능적으로는 public 필드를 사용하는 구현법과 동등하다.
public enum Singleton3 {
INSTANCE; // 이 타입의 인스턴스는 하나
public String getName() {
return "yerincho";
}
}
+ 좀 더 간결하다.
+ 직렬화가 자동으로 처리되어 여러 객체가 생길 일이 없다.
+ 리플렉션을 통한 공격에도 안전하다.
+ thread-safe
- enum 말고 다른 상위 클래스 상속해야 한다면 사용 불가 (인터페이스 구현은 가능)
추가 내용 ) if 문을 사용한 방법
public class Singleton4 {
public static RestApi getInstance() {
if(INSTANCE == null) {
INSTANCE = new RestApi();
}
return INSTANCE;
}
}
+ 인스턴스를 사용하지 않을 때는 호출되지 않는다.
- 멀티 스레드 환경에서 인스턴스가 여러 개 만들어질 수 있다.
해결 방법 1
getInstance() 에 synchronized 키워드를 붙여준다.
public static synchronized RestApi getInstance() { }
- 약 100배 정도의 성능 저하
해결 방법 2
Double-checking Locking
public class Singleton5 {
private Singleton5() { }
private volatile static Singleton5 instance; //volatile: Multi Thread 환경에서 하나의 Thread만 read & write 하고 나머지 Thread가 read하는 상황에서 적합
public static Singleton5 getInstance() {
if(instance == null){
synchronized (Singleton5.class) {
if(instance == null) {
instance = new Singleton5();
}
}
}
return instance;
}
}
+ 해결 방법 1과 비교하여 더 나은 성능 (인스턴스가 생성되기 이전에만 동기 처리됨)
해결 방법 3
Initialization on demand holder idiom
public class Singleton6 {
private Singleton6() { }
private static class SingletonHolder {
// getInstance() 호출 전에는 class 로더에 의해 실행되지 않는다.
private static final Singleton6 instance = new Singleton6();
}
public static Singleton6 getInstance() {
return SingletonHolder.instance;
}
}
+ getInstance() 가 호출될 때 SingletonHolder 가 로딩되며 인스턴스가 생성되어, 인스턴스를 사용하지 않을 때는 인스턴스를 생성하지 않음
+ 클래스 로더에서 멀티 쓰레드 문제 막아줌
'JAVA' 카테고리의 다른 글
Java Native Interface (JNI) (0) | 2021.02.26 |
---|---|
놓치고 있던 것 (0) | 2021.02.03 |