싱글톤 패턴(Singleton Pattern) 이란?

단 하나의 유일한 객체를 만들어서 사용하는 패턴이며 새로운 인스턴스를 만들지 않기 때문에 메모리 절약적인 측면에서 강점이 있다.싱글톤 패턴을 적용하는 경우로는 객체가 리소스를 많이 차지하는 무거운 역할을 가진 클래스일 때 주로 이용한다.

생성자를 private 하게 만들어 클래스 외부에서 클래스의 인스턴스를 생성하지 못하게 차단하고, 내부적으로는 단 한개의 인스턴스에 대한 접근을 제공한다.

싱글톤 패턴 사용 목적

커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 경우 인스턴스를 여러 개 만들게 되면 불필요한 자원을 사용하게 되고, 프로그램이 예상치 못한 결과를 낳을 수 있다. 따라서 객체를 필요할 때마다 생성하는 것이 아닌 단 한 번만 생성하여 전역에서 이를 공유하고 사용할 수 있게 하기 위해 싱글톤 패턴을 사용한다.

싱글톤 패턴 장 & 단점

장점

  1. 유일한 인스턴스: 싱글톤 패턴이 적용된 인스턴스는 애플리케이션 전역에 단 하나만 존재한도록 보장하고, 이는 객체이 일관된 상태의 유지 및 전역에서의 접근을 가능하게 한다.

  2. 메모리 절약: 인스턴스가 하나 뿐이므로 메모리 절약이 된다. 생성자를 여러번 호출해도 새로운 인스턴스를 생성하지 않으므로 메모리 점유 및 해제에 대한 오버헤드를 줄인다.

  3. 지연 초기화: 인스턴스가 실제로 사용되는 시점에 생성해 초기 비용을 줄인다.

단점

  1. 결합도 증가: 전역에서 접근을 허용하기 때문에 해당 인스턴스에 의존하는 경우 결합도가 증가한다.

  2. 테스트 복잡성: 단 하나의 인스턴스만을 생성하고 자원을 공유하므로 클래스 결합도가 높아질 경우 테스트가 어려워질 수 있다.

  3. 상태 관리의 어려움: 싱글톤 클래스가 상태를 갖고 있는 경우 전역에 사용되어 변경될 수 있으며, 이는 예기치 못한 일을 발생시킬 수 있다.

  4. 전역에서 접근: 애플리케이션 내 어디서든 접근이 가능할 경우, 무분별한 사용을 막기 힘들며 이로 인에 변경에 대한 복잡성이 증가한다.

싱글톤 구현 방법

싱글톤의 패턴 구현은 다양하게 존재한다. 그 중 공통되는 특징들은 다음과 같다.

  1. private 생성자만을 정의하여 외부 클래스로부터 인스턴스 생성을 차단한다.
  2. 싱글톤을 구현하고자 하는 클래스 내부에 멤버 변수로 private static 객체 변수를 만든다.
  3. public static 매서드를 통해 외부에서 싱글톤 인스턴스에 접근하도록 접점을 제공한다.

대표적인 싱글톤 구현 6가지

  1. Eager Initialization

  2. Static Block Initialization

  3. Lazy Initialization

  4. Thread Safe singleton

5. Bill Pugh Singleton Implementation (정적 내부 클래스)

6. Enum Singleton

Eager Initialization

가장 간단한 구현 방법, 싱글톤 클래스의 인스턴스를 로딩 단계에서 생성한다. 단점으로는 해당 어플리케이션에서 해당 인스턴스를 사용하지 않아도 생성하기 때문에 메모리 낭비가 발생한다.

해당 방법은 클래스가 다소 적은 리소스를 다룰때 적합하다. File System, DB connectio등 큰 리소스는 해당 방법보다 getInstance() 매서드가 호출되기 전까지 인스턴스를 생성하지 않는게 좋다. 또한 Exception Handling도 제공하지 않는다.

Static block initialization

Eager Initialization과 유사하지만 static {} 을 통해서 Exception Handling이 가능하다.
인스턴스 생성 과정에 발생하는 예외 처리는 가능해졌지만, 여전히 클래스 로딩 단계에서 인스턴스를 생성하고 있어서 단점이 있다.

Lazy Initialization

앞선 두개의 방법과 달리 호출될 때 초기화를 하는 방법이다. global access인 getInstance() 매서드를 호출할 때 인스턴스가 생성되지 않았다면 생성한다.

사용하지 않음으로 인한 인스턴스 낭비는 해결했으나, 아직 Multi-Thread 환경에서 동기화 문제를 해결하진 못했다. 만약 인스턴스가 생성되지 않았음에도, 여러 쓰레드가 동시의 getInstance()를 호출하면 하나가 아닌, 여러개의 인스턴스가 생성되어 싱글톤 패턴의 의미가 없어지는 경우가 생길 수 있다. 따라서 이 방법은 Single-Thread 환경이 보장됐을 때 쓰기 적합하다.

Thread Safe Singleton

Lazy Initialization 문제를 해결하기 위한 방법으로, getInstance() 매서드에 synchronized를 걸어두는 방식이다. synchronized 키워드는 임계 영역(Critical Section)을 형성하여 해당 영역에 오직 하나의 쓰레드만 접근 가능하게 해 줍니다.

위의 방식은 getInstance() 메서드 내에 진입하는 쓰레드가 한개로 보장받기 때문에 멀티쓰레드 환경일지라도 정상적으로 동작한다. 하지만 synchronized 키워드 자체에 대한 리소스가 크기 때문에 호출이 잦은 어플리케이션에서는 성능이 저하되는 단점이 있다.

이를 또다시 보완하기 위해 duble checked locking 방법이 있다. getInstance() 메서드 수준에 lock을 걸지 않고 instance가 null일 경우만 synchronized가 동작하게 한다.

Bill Pugh Singleton Implementation (정적 내부 클래스)

Bill Pugh가 고안한 방식으로, inner static helper class(정적 내부 클래스) 방식을 사용하는 것이다. 앞선 방식들이 안고 있는 문제점들을 대부분 해결한 방식이며, 현재 가장 널리 쓰이는 싱글톤 구현 방법 이다.

private inner static class를 둬서 싱글톤 인스턴스를 갖고 있게 한다.
SingletonHelper 클래스는 1번 방식과 2번 방식과 달리 클래스가 Load 될 때에도 Load되지 않고 있다가 getInstance()가 호출됐을 때 비로소 JVM 메모리에 로드되며, 인스턴스를 생성한다.
또한 synchronized를 사용하지 않기 때문에 4번의 문제였던 성능 저하 또한 해결했다.

초기화 지연 측면: singletonHelper 클래스가 로드될 때 JVM은 이 클래스의 정적 초기화 과정에서 단 한 번 INSTANCE를 초기화 한다. 이 과정에서 스레드는 안전하다.

스레드 안전성 측면: 클래스 로딩 과정에서 JVM은 클래스 초기화가 스레드 안전하게 이뤄지도록 보장하기 때문에 별도의 동기화 처리가 필요없다.

결론적으로 지연 초기화(Lazy Initialization)과 스레드 안전성을 자연스럽게 보장하면서 코드가 간결해졌다. 또한 메모리 소비적 측면에서 실제 인스턴스가 필요할때 까지 생성하지 않기 때문에 메모리 효율성도 갖췄다.

Enum Singleton

Enum은 싱글톤을 구현하는 가장 간단하면서도 안전한 방법이다. 이는 직렬화와 리플렉션을 통한 공격에서도 안전함을 가져온다. Java의 거장이라 불리는 Joshua Bloch는 Enum으로 싱글톤을 구현하는 방법을 제안했다.

단점은 1,2 번과 마찬가지로 사용하지 않았을 경우 메모리 문제를 해결하지 못한 것과 유연성이 떨어지는 점을 가지고 있다.

결론

모든 Singleton 구현 방법은 각기의 장 & 단점을 가지고 있다. 가장 쉽고 간단하게 singleton 을 구현하자 한다면 Enum방법이겠지만, 완성도 높고 단점이 적은 구현을 하고자 한다면 정적 내부 클래스 방법을 사용하면 좋겠다는 생각이다. 상황에 맞춰서 어떤 방식이 효율적이면서 좋을지 고민해보면 좋을 것 같다.

향후 계획

공부를 하면서 간단한 예시 코드를 작성했지만 실전에 적용한 것과는 많이 다르다는 생각이다. 언젠간?!? 실전 코드로 적용해서 구현하는 과정과 특히 Thread Safe 하지 않는 과정을 직접 테스트 해보면서 볼 계획이다. 당장은 해야할 급한 것들이 많다..

-참조-
https://readystory.tistory.com/116
https://ittrue.tistory.com/563

profile
안녕하세요~

0개의 댓글

Powered by GraphCDN, the GraphQL CDN