인스턴스를 오직 한개만 제공하는 클래스
시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다. 인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.
싱글톤 패턴을 가장 단순히 구현하는 방법
public class Settings1 {
private static Settings1 instance;
private Settings1() { }
public static Settings1 getInstance() {
if (instance == null) {
instance = new Settings1();
}
return instance;
}
}
- 생성자를 private으로 만든 이유 : 외부에서 해당 인스턴스에 직접 접근을 막기 위해
- getInstance() 메소드를 static으로 선언한 이유? : 외부에서 생성자를 통한 객체 생성을 막기 위해
- getInstance() 메소드가 멀티쓰레드 환경에서 안전하지 않은 이유? : 동시에 두 쓰레드가 해당 메소드를 호출할 경우 하나의 쓰레드가 if문 안의 로직을 수행하며 새로운 객체를 생성하기 전에 다른 쓰레드가 if문을 통과하여 다시 객체를 생성할 수 있기 때문
멀티 쓰레드 환경에서 안전하게 구현하는 방법
Syncronized
public class Settings2 {
private static Settings2 instance;
private Settings2() { }
public static synchronized Settings2 getInstance() {
if (instance == null) {
instance = new Settings2();
}
return instance;
}
}
- 자바의 동기화 블럭 처리 방법은? : 함수에 synchronized 키워드를 사용해서 한번에 하나의 쓰레드만 접근하게 한다
- getInstance() 메소드 동기화시 사용하는 락(lock)은 인스턴스의 락인가 클래스의 락인가? 그 이유는? : synchronized 키워드는 객체 단위의 락을 한다. 그 이유는 동기화를 하는 이유가 인스턴스 변수의 조작을 순차적으로 하기 위함인데 객체단위의 락을 하지 않으면 synchronized가 걸리지 않은 다른 함수에서 해당 값을 조작하거나 조회할 경우 동기화하는 이유가 없어지기 때문이다
Eager Initialize
public class Settings3 {
private static final Settings3 instance = new Settings3();
private Settings3() { }
public static Settings3 getInstance() {
return instance;
}
}
- 이른 초기화가 단점이 될 수도 있는 이유? : 이 인스턴스를 만드는 비용이 클 경우 만약 이 인스턴스를 사용하지 않는다면 생성하는 것 자체가 낭비가 될 수 있다.
- 만약 생성자에서 checked 예외를 던진다면 이 코드를 어떻게 변경해야 할까요? : 기본적으로 함수에서 checked exception을 던지면 함수를 호출하는 쪽에서 try-catch문으로 감싸야 한다. 하지만 예제처럼 변수를 초기화하는 과정에서는 try-catch문을 사용할 수 없다. 그럴경우 static {} 블록을 이용해서 instance를 초기화하면 되는데 이경우 final 키워드를 사용할 수 없다.
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;
}
}
Double Checked Locking
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;
}
}
- double check locking이라고 부르는 이유? : ckecking을 두번 했기 때문. 두개의 쓰레드가 동시에 if문을 통과했다 하더라도 synchronized때문에 하나의 쓰레드만 안으로 들어갈 수 있다. 그리고 인스턴스를 생성하고 빠져나가는데 이때 다른 if문을 통과한 쓰레드가 synchronized로 들어오더라도 인스턴스가 이미 생성되있기 때문에 if문에서 튕겨나간다.
- instance 변수는 어떻게 정의해야 하는가? 그 이유는? : 변수에 volatile 키워드를 붙여야 한다. volatile을 사용하지 않은 변수는 성능향상을 위해 CPU 캐시에 저장한다. 이 경우 쓰레드가 변수값을 읽어올 때 각각의 CPU의 캐시에서 가져오기 때문에 값이 달라 값의 불일치가 발생한다.
Static Inner Class
public class Settings5 {
private Settings5() { }
private static class Settings5Holder {
private static final Settings5 INSTANCE = new Settings5();
}
public static Settings5 getInstance() {
return Settings5Holder.INSTANCE;
}
}
- 이 방법은 static final을 썻는데도 왜 지연초기화라고 볼 수 있는가? : INSTANCE가 static final로 선언되어 있지만 해당 변수가 선언된 Setting5Holder 클래스는 getInstance() 함수가 호출될 때 로딩되기 때문에 지연 초기화로 볼 수 있다.
싱글톤 패턴 구현 방법을 깨트리는 방법
public class Destroy {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Settings5 settings1 = Settings5.getInstance();
Settings5 settings2 = Settings5.getInstance();
Constructor<Settings5> declaredConstructor = Settings5.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Settings5 settings3 = declaredConstructor.newInstance();
System.out.println(settings1 == settings2);
System.out.println(settings1 == settings3);
}
}
- 리플렉션에 대해 설명하세요 : 리플렉션은 구체적인 클래스의 타입을 몰라도 안에 선언되어있는 함수, 변수들에 접근할 수 있게 해주는 자바의 api
- setAccessible(true)를 사용하는 이유는? : 기본 생성자는 이 예제에서 private로 선언되어 있다. 즉 외부에서는 호출할 수 없다는 것인데, setAccessible(true)를 통해 Constructor<Settings5>타입으로 받은 declaredConstructor, 기본생성자를 사용가능하게해 newInstance()를 사용해 새로운 객체를 만들 수 있게하기 때문이다
public class Settings5 implements Serializable {
...
}
public class Destroy2 {
public static void main(String[] args) throws Exception {
Settings5 settings = Settings5.getInstance();
Settings5 settings1 = null;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
out.writeObject(settings);
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
settings1 = (Settings5) in.readObject();
}
System.out.println(settings == settings1);
}
}
- 자바의 직렬화 & 역직렬화에 대해 설명하세요 : 직렬화는 자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술이며 역직렬화는 byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술이다
- Serializable Id란 무엇이며 왜 쓰는가? : Serializable를 상속받는 경우 클래스의 버전관리를 위해 serialVersionUID를 사용한다. 이 serialVersionUID변수를 명시적으로 선언해 주지 않으면 컴파일러가 계산한 값을 부여하는데 Serializable Class 또는 Outer Class에 변경이 있으면 serialVersionUID값이 바뀌게 된다. 만약 Serialize할 때와 Deserialize할 때의 serialVersionUID 값이 다르면 InvalidClassExcepions가 발생하여 저장된 값을 객체로 Restore 할 수 없다
- try-resource 블럭에 대해 설명하세요 : try-resource 블럭은 기존의 try-catch-final 블럭에서 사용하고 꼭 종료해줘야 하는 resource를 사용할 때 final 블럭에서 resource를 해제하는데, try-resource 블럭을 사용하면 따로 명시적으로 resource를 해제해주지 않아도 자동으로 해제해 준다
역직렬화 대응방안
public class Settings5 implements Serializable {
...
protected Object readResolve() {
return getInstance();
}
}
역직렬화를 할 때 readResolve 메소드를 사용하는데 이를 오버라이딩하여 getInstance 함수의 리턴값을 리턴해주면 싱글톤 패턴을 지킬 수 있다.
안전하고 단순하게 구현하는 방법
public enum Settings6 {
INSTANCE;
}
public class Destroy3 {
public static void main(String[] args) throws Exception {
Settings6 settings1 = Settings6.INSTANCE;
Settings6 settings2 = null;
Constructor<?>[] declaredConstructors = Settings6.class.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
declaredConstructor.setAccessible(true);
settings2 = (Settings6) declaredConstructor.newInstance("INSTANCE");
}
System.out.println(settings1 == settings2);
}
}
Enum 클래스는 리플렉션으로 newInstance를 할 수 없다.
- Enum 타입의 인스턴스를 리플렉션을 통해 만들 수 있는가? : 없다
- Enum으로 싱글톤 타입을 구현할 때의 단점은? : 인스턴스를 미리 생성해야하며 상속이 불가하다
- 직렬화 & 역직렬화 시에 별도로 구현해야 하는 메소드가 있는가? : enum은 기본적으로 Enum이라는 클래스를 상속받고 있고 이 클래스는 이미 Serializeble을 상속하고 있기 때문에 enum도 별다른 안전장치를 마련하지 않아도 안전한 직렬화 & 역직렬화가 가능하다
정리
- 자바에서 enum을 사용하지 않고 싱글톤 패턴을 구현하는 방법은?
- private 생성자와 static 메소드를 사용하는 방법의 단점은?
- enum을 사용해 싱글톤 패턴을 구현하는 방법의 장점과 단점은?
- static inner 클래스를 사용해 싱글톤 패턴을 구현하라.
코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의
디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할
www.inflearn.com
'java' 카테고리의 다른 글
Design Patterns - Abstract Factory (0) | 2021.11.20 |
---|---|
Design Patterns - Factory Method (0) | 2021.11.17 |
함수형 인터페이스를 이용한 Builder (0) | 2021.05.29 |
더 자바(java 8) - CompletableFuture (0) | 2021.05.26 |
더 자바(java 8) - Stream, Optional, Date (0) | 2021.05.19 |
댓글