시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러 개일 때 문제가 생길 수 있는 경우가 있다.
인스턴스를 오직 한 개만 만들어 제공하는 클래스가 필요하다.
싱글톤 패턴 구현 방법 1
private 생성자에 static 메소드
생성자를 private로 만든 이유?
외부에서 new 로 새로운 객체를 만들지 못하게 하기 위함이다.
getInstance() 메소드를 static으로 선언한 이유?
Global excess가 가능하게 하기 위해서이다.
getInstance()가 멀티쓰레드 환경에서 안전하지 않은 이유?
아래에서 두 스레드가 동시에 getInstance()에 접근하면, new 를 두 번 호출할 수 있기 때문이다.
public class Settings1 {
private static Settings1 instance;
private Settings1() { }
public static Settings1 getInstance() {
if (instance == null) {
instance = new Settings1();
}
return instance;
}
}
<br><br>
## 싱글톤 패턴 구현 방법 2
### 동기화 (synchronized)를 사용해 멀티쓰레드 환경에 안전하게 만드는 방법
- getInstance()를 호출할 때 마다 synchronized를 사용하기 때문에 성능이 저하된다. (lock을 주었다가 풀었다가 반복해야하기 때문이다.)
- 자바의 동기화 블럭 처리 방법은?
* 락을 주고 푸는 방식으로 처리한다.
- getInstance() 메소드 동기화시 사용하는 락은 인스턴스의 락인가 클래스의 락인가? 이유는?
* 클래스의 락이다. 만약 인스턴스의 락이라면, 동기화시 하나의 객체를 보장할 수 없게 되기 때문이다.
``` java
public class Settings2 {
private static Settings2 instance;
private Settings2() { }
public static synchronized Settings2 getInstance() {
if (instance == null) {
instance = new Settings2();
}
return instance;
}
}
싱글톤 패턴 구현 방법 3
이른 초기화 (eager initialization)을 사용하는 방법
객체를 생성하는 비용이 많이 비싸지 않을 때 사용하면 유용할 것이다.
스레드 세이프한 방법이다.
이른 초기화가 단점이 될 수도 있는 이유?
위에서 언급했듯, 객체 생성 비용이 저렴하지 않다면, 굳이 사용하지 않을 객체를 생성해두는 꼴이 될 수 있기 때문이다.
만약 생성자에서 checked 예외를 던진다면 이 코드를 어떻게 변경해야 할까?
기본적으로 함수에서 checked exception을 던지면 함수를 호출하는 쪽에서 try-catch문으로 감싸야 한다. 하지만 예시 코드처럼 변수를 초기화하는 과정에서는 try-catch문을 사용할 수 없다. 이러한 경우 static {} 블록을 이용해서 instance를 초기화하면 되는데, 이 경우 final 키워드를 사용할 수 없다.
public class Settings3 {
private static final Settings3 INSTANCE = new Settings3();
private Settings3() { }
public static Settings3 getInstance() {
return INSTANCE;
}
}
<br>
- checked exception을 던질 때, 코드를 아래와 같이 변경할 수 있다.
``` java
public class Settings3 {
private static Settings3 instance = null;
static {
try {
instance = new Settings3();
} catch (Exception e) {
e.printStackTrace();
}
}
private Settings3() throws Exception {
throw new Exception();
}
public static Settings3 getInstance() {
return instance;
}
}
싱글톤 패턴 구현 방법 4
double checked locking으로 효율적인 동기화 블럭 만들기
동기화 블럭만 사용했을 때 갖는 비효율을, 추가적인 조건문을 두므로써 해소할 수 있다.
객체를 필요로 하는 시점에 만들 수 있다.
volatile을 사용하는 방법은 권장하는 방법 중 하나이다.
이에 대해서는 이전의 자바에서 어떻게 메모리를 사용했는지 알아야한다.
double check locking이라고 부르는 이유?
두 번 확인했기 때문이다. 스레드가 동시에 첫 번째 조건문을 만족했다해도, 동기화 블럭을 만나게 된다.
instance 변수는 어떻게 정의해야하는가? 그 이유는?
변수에 volatile 키워드를 붙여야 한다. volatile 키워드를 사용하므로써 인스턴스를 캐시가 아닌 메인 메모리에 저장하고 읽기 때문에 값 불일치 문제를 해결할 수 있다.
public class Settings4 {
private static volatile Settings4 instance;
private Settings4() { }
public static Settings4 getInstance() {
if (instance == null) {
synchronized (Settings4.class) {
if (instance == null) {
instance = new Settings4();
}
}
}
return instance;
}
}
<br><br>
## 싱글톤 패턴 구현 방법 5
### static inner 클래스를 사용하는 방법
- 스레드 세이프 하면서, lazy loading이 가능한 코드가 되는 방법
- double checked locking 보다 구현이 간단하다.
- 이 방법은 static final를 썼는데도 왜 지연 초기화 (lazy initialization)라고 볼 수 있는가?
* Inner class는 클래스가 처음 사용되는 시점에 초기화되기 때문이다. 즉, getInstance()가 호출되는 시점에 초기화 되기 때문이라고 할 수 있다.
``` java
public class Settings5 {
private Settings5() { }
private static class Settings5Holder {
private static final Settings5 INSTANCE = new Settings5();
}
public static Settings5 getInstance() {
return Settings5Holder.INSTANCE;
}
}
싱글톤 패턴 구현 깨트리는 방법 1
리플렉션을 사용한다면?
리플렉션을 사용해서 (Setting 객체 == Setting 객체) 가 false 가 되도록 객체를 생성해보자.
리플렉션에 대해 설명하시오
구체적인 클래스 타입을 알지 못해도, 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API 이다.
setAccessible(true)를 사용하는 이유는?
private 메소드(여기서는 생성자)에 접근하기 위함이다.
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException {
Settings settings = Settings.getInstance(); // 의도한대로 사용한 instance
Constructor<Settings> declaredConstructor = Settings.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Settings settings1 = declaredConstructor.newInstance(); // new를 사용해서 만든 것과 동일하다.
System.out.println(settings == settings1);
}
싱글톤 패턴 구현 깨트리는 방법 2
직렬화 & 역질렬화를 사용한다면?
자바의 직렬화 & 역질렬화에 대해 설명하시오
직렬화: 자바 시스템 내부에서 사용되는 객체 또는 데이터를 바이트(byte) 형태로 변환
역직렬화: 바이트로 변환된 데이터를 다시 객체로 변환
SerializableI란 무엇이며 왜 사용하는가?
직렬화된 클래스의 버전을 기억하여 로드된 클래스와 직렬화된 객체가 호환되는지 확인한다.
SerializableId가 다르면 역직렬화 할 수 없다. (SerializableId를 가진 클래스의 변경이 일어나면 역질렬화할 수 없게 된다.)
try-resource 블럭에 대해 설명하시오
try 코드 블럭이 끝나면 자동으로 자원을 종료해주기 때문에 명시적으로 자원 반환을 하지 않아도 된다.
public static void main(String[] args) throws IOException, ClassNotFoundException {
Settings settings = Settings.getInstance();
Settings settings1 = null;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
out.writeObject(settings);
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
settings1 = (Settings) in.readObject();
}
System.out.println(settings == settings1);
}
역직렬화 대응 방안
역직렬화에 사용되는 메소드인 readResolve()를 이용한다.
readResolve()라는 시그니처가 있으면, 역직렬화 시에 이를 실행하게 된다.
public class Settings4 {
private Settings4() { }
private static class Settings4Holder {
private static final Settings4 INSTANCE = new Settings4();
}
public static Settings4 getInstance() {
return Settings4Holder.INSTANCE;
}
<br><br>
## 싱글톤 패턴 구현 방법 6
### enum을 사용하는 방법
- 리플렉션을 사용하는 방법에 대한 대응법이 될 수 있다.
- enum도 property나 method를 얼마든지 정의해서 사용할 수 있다. (생성자는 기본이 private)
- enum 타입의 인스턴스를 리플렉션을 만들 수 있는가?
* enum 타입은 리플랙션을 할 수 없도록 막혀있기 때문에 리플랙션에 안전하다.
- enum으로 싱글톤 타입을 구현할 때의 단점은?
* 클래스가 메모리에 할당되는 시점에 인스턴스가 미리 만들어진다. (eager initialization) 초기화 시점이 문제가 되지 않다면 가장 안전한 방법이다.
* enum은 상속이 불가능하다.
- 직렬화 & 역직렬화 시에 별도로 구현해야 하는 메소드가 있는가?
* enum 타입은 enum 클래스를 상속받게 되는데, enum 클래스는 Serializable을 이미 구현하고 있기 때문에 추가적인 구현이 필요 없다.
``` java
public enum Settings5 {
INSTANCE;
}
싱글톤 패턴이란
인스턴스를 오직 한 개만 제공하는 클래스
싱글톤 패턴 구현 방법 1
private 생성자에 static 메소드
getInstance()가 멀티쓰레드 환경에서 안전하지 않은 이유?
private static Settings1 instance;
private Settings1() { }
public static Settings1 getInstance() { if (instance == null) { instance = new Settings1(); }
}
}
싱글톤 패턴 구현 방법 3
이른 초기화 (eager initialization)을 사용하는 방법
만약 생성자에서 checked 예외를 던진다면 이 코드를 어떻게 변경해야 할까?
private static final Settings3 INSTANCE = new Settings3();
private Settings3() { }
public static Settings3 getInstance() { return INSTANCE; }
}
싱글톤 패턴 구현 방법 4
double checked locking으로 효율적인 동기화 블럭 만들기
instance 변수는 어떻게 정의해야하는가? 그 이유는?
private static volatile Settings4 instance;
private Settings4() { }
public static Settings4 getInstance() { if (instance == null) { synchronized (Settings4.class) { if (instance == null) { instance = new Settings4(); } } }
}
}
싱글톤 패턴 구현 깨트리는 방법 1
리플렉션을 사용한다면?
setAccessible(true)를 사용하는 이유는?
private 메소드(여기서는 생성자)에 접근하기 위함이다.
싱글톤 패턴 구현 깨트리는 방법 2
직렬화 & 역질렬화를 사용한다면?
자바의 직렬화 & 역질렬화에 대해 설명하시오
SerializableI란 무엇이며 왜 사용하는가?
try-resource 블럭에 대해 설명하시오
역직렬화 대응 방안
private Settings4() { }
private static class Settings4Holder { private static final Settings4 INSTANCE = new Settings4(); }
public static Settings4 getInstance() { return Settings4Holder.INSTANCE; }
protected Object readResolve() { return getInstance(); }
} }
싱글톤 패턴 복습
직접 말하며 설명하고 코딩해보자
싱글톤 패턴
실무에서는 어떻게 쓰이나?