이론/백엔드 개념정리

Multi Thread를 사용할 때 싱글톤 객체 사용 및 처리 방법

블스뜸 2025. 5. 4. 21:27

멀티 스레드 환경에서 싱글톤 객체 사용에 주의해야 하는 가장 큰 이유는 **공유 자원의 동시 접근으로 인한 예기치 않은 문제 (Thread-Safety 문제)**가 발생할 수 있기 때문이다.

싱글톤 패턴은 애플리케이션 내에서 특정 클래스의 인스턴스를 단 하나만 생성하고, 어디서든 그 유일한 인스턴스에 접근할 수 있도록 보장하는데 이 유일한 인스턴스는 여러 스레드에 의해 공유될 수 있다.

문제 발생 시나리오:

  1. 공유 변수의 동시 수정: 싱글톤 객체가 **상태를 가지는 변수 (인스턴스 변수)**를 포함하고 있다고 가정하고 여러 스레드가 동시에 이 공유 변수에 접근하여 값을 수정하려고 하면 데이터 불일치 문제가 발생할 수 있다.
    • 예시: 싱글톤 객체 내에 count라는 정수형 변수가 있고, 여러 스레드가 이 변수를 증가시키는 작업을 동시에 수행한다고 가정해보면 각 스레드는 현재 count 값을 읽고 1을 더한 후 다시 저장하는 과정을 거친다. 하지만 스레드들이 동시에 이 작업을 수행하면, 한 스레드가 값을 읽어온 사이에 다른 스레드가 값을 변경해버려 최종 결과가 예상과 다르게 나올 수 있다 (Lost Update 문제).
  2. 경쟁 조건 (Race Condition): 여러 스레드가 공유 자원에 접근하는 순서나 타이밍에 따라 프로그램의 결과가 달라지는 상황을 경쟁 조건이라고 한다. 싱글톤 객체의 메서드 내에서 공유 변수를 조작하는 경우, 스레드들의 실행 순서에 따라 예상치 못한 동작이나 오류가 발생할 수 있다.
    • 예시: 싱글톤 객체의 특정 메서드가 공유 리스트에 데이터를 추가하는 작업을 수행한다고 가정하고 두 스레드가 거의 동시에 이 메서드를 호출하면, 리스트에 데이터가 추가되는 순서가 보장되지 않아 데이터의 최종 상태가 예측 불가능해질 수 있다.
  3. 데드락 (Deadlock): 싱글톤 객체가 여러 공유 자원에 접근하고, 각 스레드가 서로 다른 자원에 대해 락(Lock)을 획득한 채 다른 스레드가 획득한 락을 기다리는 상황이 발생할 수 있다. 이 경우 모든 스레드가 영원히 대기하게 되어 프로그램이 멈추는 데드락 상태에 빠질 수 있다.

왜 싱글톤이 더 위험할 수 있을까?

  • 전역 접근: 싱글톤은 어디서든 접근이 가능하므로, 여러 부분의 코드에서 공유 객체의 상태를 변경할 가능성이 높다. 이는 코드 추적 및 문제 해결을 어렵게 만든다.
  • 생명 주기: 싱글톤 객체는 애플리케이션의 생명 주기와 거의 동일한 기간 동안 존재하므로, 한 번 상태가 잘못되면 그 영향이 오랫동안 지속될 수 있다.

해결 방법:

  • Thread-Safe한 설계: 싱글톤 객체를 설계할 때부터 멀티 스레드 환경을 고려하여 공유 변수에 대한 동시 접근을 제어해야 한다.
    • Synchronization: synchronized 키워드를 사용하여 특정 메서드나 코드 블록에 대한 동시 접근을 제한한다.
    • Concurrent Collections: java.util.concurrent 패키지에서 제공하는 스레드 안전한 컬렉션 (예: ConcurrentHashMap, CopyOnWriteArrayList)을 사용한다.
    • Atomic 변수: java.util.concurrent.atomic 패키지에서 제공하는 원자적인 연산을 지원하는 변수 (예: AtomicInteger, AtomicLong)를 사용한다.
    • ThreadLocal: 각 스레드마다 독립적인 변수 복사본을 제공하여 공유 문제를 방지한다.
  • 불변 객체 (Immutable Object) 사용: 싱글톤 객체의 상태를 변경 불가능한 불변 객체로 만들면 동시 접근으로 인한 문제를 원천적으로 차단할 수 있다.
  • 싱글톤 사용 최소화: 가능한 경우 싱글톤 패턴의 사용을 줄이고, 의존성 주입 (Dependency Injection) 등을 통해 객체를 관리하는 방식을 고려한다.

결론적으로, 멀티 스레드 환경에서는 여러 스레드가 싱글톤 객체의 공유 자원에 동시에 접근하여 데이터를 조작할 수 있으므로, Thread-Safety를 보장하기 위한 주의 깊은 설계와 동기화 처리가 필수적이다. 그렇지 않으면 데이터 불일치, 경쟁 조건, 데드락과 같은 심각한 문제가 발생하여 애플리케이션의 안정성과 신뢰성을 떨어뜨릴 수 있다.