Singleton 패턴은 Class의 인스턴스를 여러 번 생성하는 것이 아닌 하나의 클래스당 하나의 인스턴스를 가지게 만들어 불 필요한 메모리 낭비를 최소화하는 디자인 패턴입니다.
Java에서 Singleton 패턴을 구현하는 방법은 여러 가지가 있고 하나씩 소개하도록 하겠습니다.
Eager Initialization
public class A {
private A instance = new A();
private A() {}
public static A getInstance() {
return instance;
}
}
가장 기본적으로 singleton으로 구현한 형태입니다. 기본 생성자를 private
로 선언하여 외부에서 생성자의 호출을 막고 오직 getInstance
함수로 인해 클래스 객체에 접근할 수 있습니다. 클래스를 최초로 접근하는 시점에 instance 변수에 객체가 할당이 되기 때문에 Thread-safe
한 방법입니다. 하지만 생성할 때 어떤 값이 필요하다면 위 방법을 사용할 수 없습니다.
Lazy Initialization
public class ShardPreferenceManager {
private ShardPreferenceManager instance = null;
private SharedPreferences preferences;
private final String PREFERENCES_NAME = "preference";
private ShardPreferenceManager() {}
private SharedPreferenceManager(Context context) {
preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
public static ShardPreferenceManager getInstance(Context context) {
if ( instance == null) instace = new ShardPreferenceManager(context);
return instance;
}
public void setString(String key, String value) {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(key, value);
editor.apply();
}
}
android에서 Singleton으로 가장 많이 구현하는 SharedPreference 클래스입니다. 객체를 생성할 때 context
객체가 필요합니다. 초기의 클래스에서는 instace가 null
로 초기화가 되어 있고 getInstace
를 호출하는 시점에서 null 일 경우 객체를 할당합니다.
코드 상으로는 한 번만 할당하는 것으로 보이지만 멀티 쓰레드 환경에서는 보장해주지 못합니다. 다중 스레드가 null인 상태의 instance 변수에 동시에 접근할 경우 둘 다 새로운 객체를 생성하기 때문입니다.
Thread-Safe Singleton
public class ShardPreferenceManager {
private ShardPreferenceManager instance = null;
private SharedPreferences preferences;
private final String PREFERENCES_NAME = "preference";
private ShardPreferenceManager() {}
private SharedPreferenceManager(Context context) {
preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
public static synchronized ShardPreferenceManager getInstance(Context context) {
if ( instance == null) instace = new ShardPreferenceManager(context);
return instance;
}
public void setString(String key, String value) {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(key, value);
editor.apply();
}
}
위 방법은 synchronized
키워드를 사용하여 getInstace
에 접근하는 스레드의 수를 하나로 고정하는 것입니다.
하나의 스레드가 해당 함수에 접근 중이라면 lock
이 걸려 나머지 스레드는 wait
상태가 됩니다. 결과적으로 동시에
여러 스레드가 접근하지 못하는 것으로 Singleton 패턴을 보장합니다. 하지만 단점이 존재합니다.
멀티 쓰레드 환경에서 getInstace
의 호출이 빈번하다면 그만큼 wait
시간이 길어지게 되므로 시스템의 성능 부하가 올 수 있습니다.
Double Checked Locking
public class ShardPreferenceManager {
private volatile ShardPreferenceManager instance = null;
private SharedPreferences preferences;
private final String PREFERENCES_NAME = "preference";
private ShardPreferenceManager() {}
private SharedPreferenceManager(Context context) {
preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
public static ShardPreferenceManager getInstance(Context context) {
if ( instance == null) {
synchronized (ShardPreference.class) {
if (instance == null) {
instance = new ShardPreferenceManager(context);
}
}
}
return instance;
}
public void setString(String key, String value) {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(key, value);
editor.apply();
}
}
synchronized
를 instace가 null일 경우에만 lock
을 거는 것으로 시스템의 성능 부하를 완하 시킬 수 있습니다.
참고
'Language > Java' 카테고리의 다른 글
[Java] Thread 강제 종료 시키는 방법 (0) | 2021.05.11 |
---|---|
Java - String pool (0) | 2020.05.28 |