싱글톤(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;
}

 

특징:

  1. 간단한 구현: 구현이 간단하며 직관적입니다.
  2. 전체 메서드 동기화: getInstance() 메서드 전체가 synchronized로 감싸져 있어, 여러 스레드가 동시에 접근하더라도 인스턴스가 하나만 생성되도록 보장합니다.
  3. 성능 문제: synchronized로 인해 성능 저하가 발생할 수 있습니다. 인스턴스가 이미 생성된 후에도 getInstance() 호출 시마다 synchronized가 적용되어, 스레드 간의 경합으로 인해 불필요한 성능 저하가 발생할 수 있습니다.
  4. 사용 시점: 성능 저하가 큰 문제가 되지 않는 간단한 애플리케이션에서 사용하기 적합합니다.

 

사용에 적합한 앱 예시

  1. 설정 관리 앱 (Settings Manager App):
    • 앱의 설정값을 전역적으로 관리하는 SettingsManager 싱글톤 클래스가 있는 경우, 앱의 초기화 단계에서 한 번만 호출되고 이후 빈번하게 접근하지 않는다면 이 방식이 적합합니다.
    • 예를 들어, 사용자가 앱의 테마, 알림 설정 등을 변경할 때만 getInstance()를 호출합니다. 이러한 호출은 빈번하지 않으며, 설정값이 적재된 후에는 getInstance() 메서드 호출이 거의 일어나지 않습니다.
    • 이러한 앱에서는 성능보다는 코드의 간결성과 구현의 용이성이 더 중요하기 때문에 2번 방식이 유용합니다.
  2. 단순한 로그 관리 앱 (Simple Logging App):
    • 로그를 관리하는 Logger 클래스가 싱글톤으로 구현된 경우, 로그 파일이 생성되고 기록되는 동안에는 큰 부하가 없고, 앱이 단일 스레드 환경에서 작동하는 경우가 많습니다.
    • 예를 들어, 비동기 작업이 거의 없는 단순한 로그 기록 앱에서 로그 파일에 기록하는 경우, Logger 싱글톤 클래스의 인스턴스 접근이 빈번하지 않고, 스레드 경합이 없으므로 이 방식이 적합합니다.
  3. 싱글톤 리소스 관리자 앱 (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;
}

 

특징:

  1. 성능 최적화: getInstance() 메서드에서 처음 체크할 때 인스턴스가 이미 생성된 경우에는 동기화를 피할 수 있어, 성능 저하를 최소화할 수 있습니다. 이는 인스턴스를 생성한 이후 대부분의 호출에서 synchronized 블록에 들어가지 않기 때문에 효율적입니다.
  2. 복잡한 구현: 코드가 비교적 복잡하며, volatile 키워드와 두 번의 체크를 통해 동기화의 오버헤드를 줄이는 메커니즘을 이해해야 합니다.
  3. 멀티스레드 안전성: volatile 키워드를 통해 JVM의 메모리 모델에서 발생할 수 있는 문제를 방지하며, 멀티스레드 환경에서 안전하게 작동합니다.
  4. 사용 시점: 성능이 중요한 멀티스레드 애플리케이션에서 사용하기 적합합니다. 특히, 인스턴스 생성 이후 getInstance() 메서드가 빈번히 호출되는 경우에 효과적입니다.

 

사용에 적합한 앱 예시

  1. 멀티스레드 네트워크 통신 앱 (Multithreaded Networking App):
    • 여러 스레드가 동시에 네트워크 요청을 보내고 처리하는 앱에서는 NetworkManager 싱글톤 클래스가 필요할 수 있습니다.
    • 예를 들어, 앱에서 다수의 비동기 네트워크 요청을 처리하고, 요청을 큐에 넣거나 취소하고 결과를 콜백으로 전달할 때 NetworkManager 싱글톤 인스턴스에 자주 접근해야 합니다.
    • 이 경우, 성능이 중요한 이슈가 되며, 인스턴스가 한 번 생성된 이후에는 성능 오버헤드 없이 빠르게 접근할 수 있는 3번 방식이 적합합니다.
  2. 게임 상태 관리 앱 (Game State Manager):
    • 게임에서 모든 스레드가 현재 상태를 필요로 하며, 빠른 상태 변경 및 조회가 필요할 수 있습니다. 이 때 GameStateManager가 싱글톤으로 구현될 수 있습니다.
    • 예를 들어, 여러 스레드가 캐릭터 상태, 게임 점수 등을 관리하고 업데이트하는 경우, 상태 조회 및 업데이트가 빈번히 발생하고 성능이 중요합니다. 이때 3번 방식이 유용합니다.
  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

+ Recent posts