싱글톤(Singleton)패턴
인스턴스를 오직 한개만 제공하는 클래스
싱글톤 패턴을 가장 단순히 구현하는 방법
시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다.
인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.
public class App {
public static void main(String[] args){
//Setting이라는 클래스의 타입의 인스턴스를 여러개 생성할수있다.
//new를 이용해서 만들면 새로운 인스턴스가 만들어진다.
Setting settings = new Settings();
Setting settings1 = new Settings();
//new로 생성한것들은 서로 같지않다.
System.out.println(settings != settings1); //true
}
}
Java
복사
public class Settings {
}
Java
복사
싱글톤 패턴을 사용할려면 절대 new를 사용하면 안된다.
new 생성을 하지 못하게
public class Settings {
//이 해당하는 클래스 안에서만 접근할 수 있게 하는 생성자를 만들어준다.
private Settings() {}
//글로벌엑세스가 가능하게 할려면
public static Settings getInstance() {
return new Settings();
}
}
Java
복사
public class App {
public static void main(String[] args){
Setting settings = Settings.getInstance();
Setting settings1 = Settings.getInstance();
//결국 이것도 Settings 클래스의 static 안에서도 new로 진행했기 때문에
// 다른 객체라고 인식한다.(생성한것들은 서로 같지않다.)
System.out.println(settings != settings1); //true
}
}
Java
복사
우린 현재 인스턴스가 같기를 바라고 있는데 자꾸 다른객체로 인식하고 있다.
public class Settings {
private static Settings instance;
private Settings() {}
public static Settings getInstance() {
if(instance == null){
instance = new Settings();
}
return instance;
}
}
Java
복사
public class App {
public static void main(String[] args){
Setting settings = Settings.getInstance();
//매번 호출해도 같은 인스턴스로 인식한다.
System.out.println(settings == Settings.getInstance()); //true
}
}
Java
복사
이 방법이 가장 괜찮은 방법이지만 이 방법에 심각한 문제가 하나 있다.
앱 애플리케이션을 만들다보면 멀티쓰레드로 만들어진다.
멀티쓰레드 환경에서 이 코드가 안전하지 않다.
멀티쓰레드 환경에서 안전하게 싱글톤을 생성하는 방법
sychronized 키워드 사용하기
public class Settings {
private static Settings instance;
private Settings() {}
//sychronized 사용해서 이 메소드 안에서 하나의 쓰레드만 들어오게 한다.
// 하나의 인스터스만 보장
// 단점은 getInstance()를 호출할때마다 성능의 불이득이 있을 수 있다.
// 동기화 자체가 key가 되는 lock을 잡아서 lock을 가지고 있는 쓰레드만 영역에 접근할수있게 하기때문에
// 다 쓰면 lock을 해제하기때문에 성능 부화가 생긴다.
public static sychronized Settings getInstance() {
if(instance == null){
instance = new Settings();
}
return instance;
}
}
Java
복사
이른 초기화(eager initialization) 사용하기
근데 만약에 getInstance() 메소드 안의 instance = new Settings(); 객체를 꼭 나중에 만들지 않아도 된다 하면은 미리 만들 수도 있다.
public class Settings {
private static final Settings INSTANCE = new Settings();
private Settings() {}
public static Settings getInstance() {
return INSTANCE;
}
}
Java
복사
이 방법은 바로 INSTANCE를 리턴해주기 때문에 Settings클래스가 로딩되는 시점에 static의 필드들이 초기화 되면서 멀티쓰레드 환경에서도 안전한 방법이 된다.
다만 단점은 미리 만든다는게 단점이 될 수 있다.
만약 이 인스턴스를 만드는 과정이 길고 오래걸리고 메모리를 많이 사용한다면 만들어 놨는데 사용하지 않는다면
(애플리케이션 로딩할때 오래걸리는데 막상 사용하지않는다?…그건..)
여기서 이 인스턴스를 사용이 될 때 만들고 싶다! 근데 sychronized 비용이 너무 신경쓰이면
double checked locking 사용하기
효울적인 동기화 블럭 만들기
public class Settings {
//volatile 키워드를 사용해야지만 자바1.5 이상부터 동작하는 double checked locking 사용 가능
private static volatile Settings instance;
private Settings() {}
public static Settings getInstance() {
if(instance == null){
//Settings의 class를 lock으로 이용해주고
sychronized(Settings.class){
if(instance == null){
instance = new Settings();
}
}
}
return instance;
}
}
Java
복사
instance을 필요로 하는 시점에 만들 수 있다는 큰 장점이 있다.
문제는 사실 이 기법이 복잡한 기법이다.
volatile 을 사용하는 방법까지도 이해하면 좋지만 너무 깊게 빠지게 되고
자바 1.4를 사용하는 사람도 있을 수 있다(사실 거의 희박하지만)
그래서 다른방법 중에 권장하는 방법 중 하나인
static inner 클래스 사용하기
public class Settings {
private Settings() {}
private static class SettingsHolder {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.INSTANCE;
}
}
Java
복사
멀티쓰레드 환경에서도 안전하고 getInstance()가 호출될 때 Settings클래스가 로딩이 되고 SettingsHolder가 만들어지면서 lazy loading도 가능한 코드가 된다
(lazy loading이란 사용자가 필요로 하는 시점에 로딩하는 방법)
이 모든 방법을 깨트릴수있는 다양한 방법이 존재하는데…