싱글톤(Singleton) 패턴은 클래스의 인스턴스가 하나만 생성되도록 보장하는 디자인 패턴입니다. 이를 통해 전역적으로 접근 가능한 객체를 만들 수 있습니다. 안드로이드 자바에서 싱글톤 패턴을 구현하는 주요 구조를 설명하겠습니다.
1. 기본 싱글톤 구조
public class Singleton {
// 싱글톤 인스턴스를 보관하는 정적 변수
private static Singleton instance = null;
// private 생성자를 통해 외부에서 객체를 생성하지 못하도록 방지
private Singleton() {
// 초기화 코드
}
// 인스턴스를 반환하는 정적 메서드
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2. 스레드 안전한 싱글톤 (Lazy Initialization)
멀티스레드 환경에서 안전하게 싱글톤을 구현하려면 synchronized 키워드를 사용하여 동시에 여러 스레드가 접근하는 문제를 방지해야 합니다.
public class Singleton {
private static Singleton instance = null;
private Singleton() {
// 초기화 코드
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. 더블 체크 락킹(Double-Checked Locking)
스레드 안전성을 유지하면서 성능을 개선하기 위해 더블 체크 락킹 기법을 사용할 수 있습니다.
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
// 초기화 코드
}
public static Singleton getInstance() {
if (instance == null) { // 첫 번째 체크
synchronized (Singleton.class) {
if (instance == null) { // 두 번째 체크
instance = new Singleton();
}
}
}
return instance;
}
}
4. 정적 블록을 사용한 초기화
정적 블록을 사용하여 클래스 로딩 시점에 인스턴스를 생성할 수 있습니다. 이 방법은 클래스가 로딩될 때 미리 인스턴스를 생성하므로 멀티스레드 환경에서도 안전합니다.
public class Singleton {
private static final Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {
// 초기화 코드
}
public static Singleton getInstance() {
return instance;
}
}
5. Enum을 이용한 싱글톤 구현
Enum 타입은 기본적으로 싱글톤을 보장하므로, 가장 간단하고 안전한 방식 중 하나입니다.
public enum Singleton {
INSTANCE;
public void someMethod() {
// 메서드 구현
}
}
2번과 3번의 특징과 사용에 적절한 예시입니다.
2번: 스레드 안전한 싱글톤 (Lazy Initialization with Synchronized)
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
특징:
- 간단한 구현: 구현이 간단하며 직관적입니다.
- 전체 메서드 동기화: getInstance() 메서드 전체가 synchronized로 감싸져 있어, 여러 스레드가 동시에 접근하더라도 인스턴스가 하나만 생성되도록 보장합니다.
- 성능 문제: synchronized로 인해 성능 저하가 발생할 수 있습니다. 인스턴스가 이미 생성된 후에도 getInstance() 호출 시마다 synchronized가 적용되어, 스레드 간의 경합으로 인해 불필요한 성능 저하가 발생할 수 있습니다.
- 사용 시점: 성능 저하가 큰 문제가 되지 않는 간단한 애플리케이션에서 사용하기 적합합니다.
사용에 적합한 앱 예시
- 설정 관리 앱 (Settings Manager App):
- 앱의 설정값을 전역적으로 관리하는 SettingsManager 싱글톤 클래스가 있는 경우, 앱의 초기화 단계에서 한 번만 호출되고 이후 빈번하게 접근하지 않는다면 이 방식이 적합합니다.
- 예를 들어, 사용자가 앱의 테마, 알림 설정 등을 변경할 때만 getInstance()를 호출합니다. 이러한 호출은 빈번하지 않으며, 설정값이 적재된 후에는 getInstance() 메서드 호출이 거의 일어나지 않습니다.
- 이러한 앱에서는 성능보다는 코드의 간결성과 구현의 용이성이 더 중요하기 때문에 2번 방식이 유용합니다.
- 단순한 로그 관리 앱 (Simple Logging App):
- 로그를 관리하는 Logger 클래스가 싱글톤으로 구현된 경우, 로그 파일이 생성되고 기록되는 동안에는 큰 부하가 없고, 앱이 단일 스레드 환경에서 작동하는 경우가 많습니다.
- 예를 들어, 비동기 작업이 거의 없는 단순한 로그 기록 앱에서 로그 파일에 기록하는 경우, Logger 싱글톤 클래스의 인스턴스 접근이 빈번하지 않고, 스레드 경합이 없으므로 이 방식이 적합합니다.
- 싱글톤 리소스 관리자 앱 (Singleton Resource Manager):
- 앱에서 특정 리소스(예: DB 연결, 파일 읽기/쓰기 등)를 관리하는 클래스가 싱글톤으로 구현된 경우, 이 리소스가 자주 접근되지 않는다면 2번 방식이 유용할 수 있습니다.
- 예를 들어, 간단한 오프라인 데이터베이스를 사용해 자주 변경되지 않는 설정 데이터를 저장하고 읽어오는 앱에서, DB 접속 인스턴스에 대한 접근이 빈번하지 않다면 성능 저하가 크지 않기 때문에 2번 방식이 적합합니다.
3번: 더블 체크 락킹 (Double-Checked Locking)
public static Singleton getInstance() {
if (instance == null) { // 첫 번째 체크
synchronized (Singleton.class) {
if (instance == null) { // 두 번째 체크
instance = new Singleton();
}
}
}
return instance;
}
특징:
- 성능 최적화: getInstance() 메서드에서 처음 체크할 때 인스턴스가 이미 생성된 경우에는 동기화를 피할 수 있어, 성능 저하를 최소화할 수 있습니다. 이는 인스턴스를 생성한 이후 대부분의 호출에서 synchronized 블록에 들어가지 않기 때문에 효율적입니다.
- 복잡한 구현: 코드가 비교적 복잡하며, volatile 키워드와 두 번의 체크를 통해 동기화의 오버헤드를 줄이는 메커니즘을 이해해야 합니다.
- 멀티스레드 안전성: volatile 키워드를 통해 JVM의 메모리 모델에서 발생할 수 있는 문제를 방지하며, 멀티스레드 환경에서 안전하게 작동합니다.
- 사용 시점: 성능이 중요한 멀티스레드 애플리케이션에서 사용하기 적합합니다. 특히, 인스턴스 생성 이후 getInstance() 메서드가 빈번히 호출되는 경우에 효과적입니다.
사용에 적합한 앱 예시
- 멀티스레드 네트워크 통신 앱 (Multithreaded Networking App):
- 여러 스레드가 동시에 네트워크 요청을 보내고 처리하는 앱에서는 NetworkManager 싱글톤 클래스가 필요할 수 있습니다.
- 예를 들어, 앱에서 다수의 비동기 네트워크 요청을 처리하고, 요청을 큐에 넣거나 취소하고 결과를 콜백으로 전달할 때 NetworkManager 싱글톤 인스턴스에 자주 접근해야 합니다.
- 이 경우, 성능이 중요한 이슈가 되며, 인스턴스가 한 번 생성된 이후에는 성능 오버헤드 없이 빠르게 접근할 수 있는 3번 방식이 적합합니다.
- 게임 상태 관리 앱 (Game State Manager):
- 게임에서 모든 스레드가 현재 상태를 필요로 하며, 빠른 상태 변경 및 조회가 필요할 수 있습니다. 이 때 GameStateManager가 싱글톤으로 구현될 수 있습니다.
- 예를 들어, 여러 스레드가 캐릭터 상태, 게임 점수 등을 관리하고 업데이트하는 경우, 상태 조회 및 업데이트가 빈번히 발생하고 성능이 중요합니다. 이때 3번 방식이 유용합니다.
- 멀티스레드로 동작하는 데이터 분석 앱 (Multithreaded Data Analysis App):
- 여러 데이터 분석 작업을 병렬로 수행하고, 이를 통합 관리하는 DataAnalyzer 싱글톤 클래스가 필요한 앱입니다.
- 예를 들어, 대량의 데이터를 동시에 여러 스레드에서 분석하고, 분석된 결과를 합산 및 요약하는 앱에서, DataAnalyzer 싱글톤 인스턴스는 여러 스레드에서 접근하므로 빠른 접근이 필요합니다.
- 이 경우, 빈번한 접근 시 동기화 오버헤드가 발생하지 않도록 하기 위해 3번 방식이 적합합니다.
무료 다운로드 많이 이용해 주세요
전국 CCTV 지도(교통카메라지도) - Google Play 앱
고속도로 교통 카메라, 국도 교통 카메라, 즐겨찾기, 공공기관 관리 CCTV 위치를 지도에서 검색
play.google.com
2번: 스레드 안전한 싱글톤 (Lazy Initialization with Synchronized) 코드예시
이 예제에서는 SettingsManager 클래스가 싱글톤으로 구현되어 앱의 설정을 관리합니다. 각기 다른 스레드에서 동일한 인스턴스에 접근해 설정 값을 저장하고 불러오는 샘플을 포함하고 있습니다.
SettingsManager 클래스
import android.content.Context;
import android.content.SharedPreferences;
public class SettingsManager {
// 싱글톤 인스턴스를 보관하는 정적 변수
private static SettingsManager instance = null;
// SharedPreferences 키
private static final String PREF_NAME = "app_settings";
private static final String KEY_APP_THEME = "app_theme";
private static final String KEY_NOTIFICATION_ENABLED = "notification_enabled";
private static final String KEY_USER_NAME = "user_name";
// 예제 필드
private String appTheme;
private boolean notificationEnabled;
private String userName;
// SharedPreferences
private SharedPreferences sharedPreferences;
// private 생성자를 통해 외부에서 객체를 생성하지 못하도록 방지
private SettingsManager(Context context) {
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
// 초기화: SharedPreferences에서 값 로드
appTheme = sharedPreferences.getString(KEY_APP_THEME, "Light");
notificationEnabled = sharedPreferences.getBoolean(KEY_NOTIFICATION_ENABLED, true);
userName = sharedPreferences.getString(KEY_USER_NAME, "Guest");
}
// 인스턴스를 반환하는 정적 메서드
public static synchronized SettingsManager getInstance(Context context) {
if (instance == null) {
instance = new SettingsManager(context);
}
return instance;
}
// 테마를 설정하는 메서드
public void setAppTheme(String theme) {
this.appTheme = theme;
sharedPreferences.edit().putString(KEY_APP_THEME, theme).apply();
}
// 현재 테마를 반환하는 메서드
public String getAppTheme() {
return appTheme;
}
// 알림 설정을 활성화/비활성화하는 메서드
public void setNotificationEnabled(boolean enabled) {
this.notificationEnabled = enabled;
sharedPreferences.edit().putBoolean(KEY_NOTIFICATION_ENABLED, enabled).apply();
}
// 알림 설정 상태를 반환하는 메서드
public boolean isNotificationEnabled() {
return notificationEnabled;
}
// 사용자 이름을 설정하는 메서드
public void setUserName(String userName) {
this.userName = userName;
sharedPreferences.edit().putString(KEY_USER_NAME, userName).apply();
}
// 사용자 이름을 반환하는 메서드
public String getUserName() {
return userName;
}
}
사용 예제
* 이 코드에서는 MainActivity의 메인 스레드와 새로운 스레드가 동일한 SettingsManager 인스턴스를 사용하여 설정 값을 공유합니다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// SettingsManager 인스턴스 가져오기 (ApplicationContext 사용)
SettingsManager settingsManager = SettingsManager.getInstance(getApplicationContext());
// 메인 스레드에서 테마 설정
settingsManager.setAppTheme("Dark");
Log.d("SettingsManager", "Theme set to: " + settingsManager.getAppTheme());
// 메인 스레드에서 알림 설정 비활성화
settingsManager.setNotificationEnabled(false);
Log.d("SettingsManager", "Notifications Enabled: " + settingsManager.isNotificationEnabled());
// 메인 스레드에서 사용자 이름 설정
settingsManager.setUserName("John Doe");
Log.d("SettingsManager", "User Name set to: " + settingsManager.getUserName());
// 새로운 스레드에서 설정 값 확인
new Thread(new Runnable() {
@Override
public void run() {
SettingsManager settingsManager = SettingsManager.getInstance(getApplicationContext());
Log.d("SettingsManager", "Current Theme: " + settingsManager.getAppTheme());
Log.d("SettingsManager", "Notifications Enabled: " + settingsManager.isNotificationEnabled());
Log.d("SettingsManager", "User Name: " + settingsManager.getUserName());
}
}).start();
}
}
3번: 더블 체크 락킹 (Double-Checked Locking) 코드 예시
이 예제에서는 NetworkManager 클래스가 싱글톤으로 구현되어 네트워크 요청을 관리합니다. 여러 스레드가 동시에 네트워크 요청을 보낼 때 유일한 인스턴스를 사용하도록 보장합니다.
NetworkManager 클래스
import java.util.LinkedList;
import java.util.Queue;
public class NetworkManager {
// 싱글톤 인스턴스를 보관하는 정적 변수 (volatile 키워드 사용)
private static volatile NetworkManager instance = null;
// 네트워크 요청을 저장하는 큐
private Queue<String> requestQueue;
// private 생성자를 통해 외부에서 객체를 생성하지 못하도록 방지
private NetworkManager() {
this.requestQueue = new LinkedList<>();
}
// 인스턴스를 반환하는 정적 메서드 (더블 체크 락킹 사용)
public static NetworkManager getInstance() {
if (instance == null) { // 첫 번째 체크
synchronized (NetworkManager.class) {
if (instance == null) { // 두 번째 체크
instance = new NetworkManager();
}
}
}
return instance;
}
// 네트워크 요청을 큐에 추가하는 메서드
public synchronized void addRequest(String url) {
requestQueue.add(url);
System.out.println("Request added to queue: " + url);
}
// 네트워크 요청을 순차적으로 처리하는 메서드
public synchronized void processNextRequest() {
if (!requestQueue.isEmpty()) {
String nextRequest = requestQueue.poll();
// 실제 네트워크 요청을 보내는 코드가 여기에 위치 (가상 처리)
System.out.println("Processing request: " + nextRequest);
} else {
System.out.println("No requests to process.");
}
}
}
사용 예제
*이 코드에서는 MainActivity의 메인 스레드와 새로운 스레드가 동일한 NetworkManager 인스턴스를 사용하여 각각 다른 네트워크 요청을 보냅니다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 메인 스레드에서 네트워크 요청 추가
NetworkManager networkManager = NetworkManager.getInstance();
networkManager.addRequest("https://example.com/request1");
// 새로운 스레드에서 네트워크 요청 추가
new Thread(new Runnable() {
@Override
public void run() {
NetworkManager networkManager = NetworkManager.getInstance();
networkManager.addRequest("https://example.com/request2");
}
}).start();
// 메인 스레드에서 네트워크 요청 처리
new Thread(new Runnable() {
@Override
public void run() {
try {
// 1초 간격으로 요청 처리
while (true) {
NetworkManager networkManager = NetworkManager.getInstance();
networkManager.processNextRequest();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
이상.
무료 다운로드 많이 이용해 주세요
날씨위성영상 라이브 - (태풍 구름 눈 비 CCTV) - Google Play 앱
실시간으로 위성영상, 레이더영상을 확인하세요
play.google.com
'coding > Singleton 싱글톤' 카테고리의 다른 글
싱글톤(Singleton) 패턴 사용처 (2) | 2024.09.30 |
---|