<백기선 강사님 강의 요약>
Singleton Pattern을 공부해보자
public class App {
public static void main(String[] args){
Settings settings1 = new Settings();
Settings settings2 = new Settings();
System.out.println(settings == settings1);
}
}
Settings에 대해 여러 instance가 만들어진다.
인스턴스를 오직 하!나!만 제공해주는 클래스가 필요하다.
하나의 Settings instance를 만들어주고, Settings가 필요할 때 만들어둔 instance를 리턴하자.
public class Settings {
private static Settings instance;
private Settings(){}
public static Settings getInstance(){
if(instance == null){
instance = new Settings();
}
return instance;
}
}
⚠️ 여기서 다시 Settings 인스턴스 2개를 생성한다면?
public class App {
public static void main(String[] args){
Settings settings1 = Settings.getInstance();
Settings settings2 = Settings.getInstance();
System.out.println(settings1 == settings2);
}
}
✅ 싱글턴의 필요성
시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는
경우가 있다. 인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요할 때 사용
멀티스레드에서 안전하지 않아!
생성자를 private으로 만든 이유?
A) 외부에서 생성을 못하게 하기 위해 ( = 내부에서만 생성하기 위해 )
getInstance() 메소드를 static으로 선언한 이유?
A) 외부에서 인스턴스에 접근하기 위해
getInstance()가 멀티쓰레드 환경에서 안전하지 않은 이유?
A) 두 개의 thread가 getInstance의 if문에 동시에 접근한다면 2개의 instance가 생성될 수 있음!
그럼 어떻게 thread safe하게 구현할 수 있을까?
=> synchronized를 사용해서, 하나의 스레드만 메소드를 실행할 수 있게 하자!(Thread safe!)
public static synchronized Settings getInstance(){
if(instance == null){
instance = new Settings();
}
return instance;
}
✅ 장점 : 동시에 여러 스레드가 접근할 수 없음
❎ 단점 : 성능 저하
Q) 생각해봅시다.
1. 자바의 동기화 블럭 처리 방법은?
A) 잘 정리된 링크[https://github.com/kim-se-jin/CS-JAVA-Study/blob/main/Java/Synchronization.md]
(GPT 답변)
인스턴스의 락 : 인스턴스 메소드에서 synchronized 키워드를 사용하면, 그 메소드를 호출하는 특정 인스턴스에 대한 락이 적용됩니다. 즉, 같은 인스턴스의 다른 스레드가 해당 메소드에 동시에 접근할 수 없습니다.
클래스 락 : static synchronized 메소드는 클래스 전체에 적용되므로, 클래스 객체에 대한 락이 걸립니다. 클래스 객체에 대한 락은 해당 클래스의 모든 static synchronized 메소드들이 공유합니다. 즉, 한 스레드가 static synchronized 메소드를 실행하고 있을 때, 다른 스레드는 그 클래스의 다른 static synchronized 메소드에 접근할 수 없습니다.
흠.. 그럼 synchronized를 안쓰는 방법은 없을까?
객체를 final로 하나 만들어두고, getInstance()를 호출할 때 생성해둔 객체를 return 해주자!
public class Settings {
private static final Settings INSTANCE = new Settings();
private Settings(){}
public static Settings getInstance(){
return INSTANCE;
}
}
✅ 장점 : 동시에 여러 스레드가 접근할 수 없음 ( = 스레드 safe함 )
❎ 단점 : 인스턴스 만드는 과정이 오래 걸리거나, 생성 이후 안쓰게 되는 상황 등이 있을 수 있음.
변수를 선언할 때 생성해, INSTANCE 변수가 클래스 로딩 시점에 초기화됨 -> final을 사용해, 변수가 초기화된 이후 변경될 수 없음(= 변수에 값을 한 번만 할당할 수 있음) -> thread safe 해짐
Q) 생각해 봅시다.
1. 이른 초기화가 단점이 될 수도 있는 이유?
A) 위 ❎ 단점 내용
그럼 Synchorinized를 최소한으로 쓰고, 내가 쓰고싶을 때 만들 수 있는 방법은 없나?
public class Settings {
private static volatile Settings instance;
private Settings(){}
public static Settings getInstance(){
if(instance == null){
synchronized (Settings.class){
if(instance == null){
instance = new Settings();
}
}
}
return instance;
}
}
✅ 장점 : 동시에 여러 스레드가 접근할 수 없고, 인스턴스를 사용할 때 생성함.
❎ 단점 : 코드가 복잡함, volatile은 자바 1.5 부터 사용할 수 있음.
Q)
1. double check locking이라고 부르는 이유?
private static volatile Settings instance;
그럼 1.4에서나 코드를 간결하게 쓰고싶으면 어떡하지?
public class Settings {
private Settings(){}
private static final class InstanceHolder {
private static final Settings instance = new Settings();
}
public static Settings getInstance(){
return InstanceHolder.instance;
}
}
✅ 장점 : double checked locking과 동일하지만 코드가 더 간결!
이 방법을 깨트릴 다양한 방법이 존재
Q) 생각해 봅시다.
1.이 방법은 static final를 썼는데도 왜 지연 초기화 (lazy initialization)라고 볼 수 있는가?
( 지연 초기화란? 인스턴스를 처음 사용할 때까지 인스턴스를 생성하지 않고, 필요할 때 인스턴스를 생성하는 방법 )
A) SettingsHolder 클래스 내부에 SETTINGS라는 static final 변수가 선언되어 있고, 이 변수는 Settings 클래스가 처음으로 로드될 때가 아니라, getInstance() 메소드가 호출될 때 생성되기 때문!