Skip to content

Latest commit

 

History

History
185 lines (132 loc) · 8.43 KB

File metadata and controls

185 lines (132 loc) · 8.43 KB

Singleton pattern 이란 무엇이고, 어떤 장점과 단점이 있을까요?

엘리

Singleton pattern

싱글톤 패턴은 어떤 클래스를 어플리케이션 내에서 하나의 인스턴스만 생성하여 사용하도록 강제하는 디자인 패턴입니다.

생성자가 여러번 호출되도 실제로 생성되는 객체는 하나이며, 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만듭니다.

Java에서는 두가지 방식으로 싱글턴을 만들 수 있습니다.

  1. private 생성자 + public static final 필드 방식
    public class MySingleton {
        
        public static final MySingleton INSTANCE = new MySingleton();
        
        private MySingleton() {
        }
    }
  2. private 생성자 + 정적 팩터리 방식
    public class MySingleton {
    
        private static final MySingleton INSTANCE = new MySingleton();
    
        private MySingleton() {
        }
        
        public static MySingleton getInstance() {
            return INSTANCE;
        }
    }
    // 지연 초기화 적용
    public class MySingleton {
    
        private static MySingleton INSTANCE;
    
        private MySingleton() {
        }
    
        public static MySingleton getInstance() {
            if (INSTANCE == null) {
                INSTANCE = new MySingleton();
            }
            return INSTANCE;
        }
    }

장점

  • 객체를 생성할 때마다 메모리 영역을 할당받아야 합니다. 하지만 싱글톤 패턴을 적용하면 한번 객체를 생성한 이후에는 이를 재사용하기 때문에 메모리 낭비를 방지할 수 있습니다.
  • 싱글톤으로 구현한 인스턴스에는 전역적으로 접근할 수 있으므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능해집니다.

무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 싱글톤으로 만들어 사용할 수 있습니다. (컨트롤러, 서비스, DB 커넥션풀, 스레드풀, 로그 기록 객체 등)

단점

객제지향과 맞지 않다.

private 생성자를 사용하기 때문에 상속을 이용할 수 없으며, 이를 이용한 다형성 또한 적용할 수 없습니다.

테스트를 어렵게 한다.

private 생성자를 사용하기 때문에 인터페이스를 구현한 싱글톤 객체가 아니라면 Mock 객체를 만들기 어려워 단위 테스트를 진행하는 데 어려움이 있을 수 있습니다.

  • Mockito는 정적 메서드 모킹을 지원하지 않는다. (PowerMock은 가능)

멀티 스레드 환경에서 동기화 처리를 해주지 않으면, 인스턴가 1개 이상 생성되는 문제가 발생할 수 있다.

지연 초기화를 적용해 싱글톤을 구현하는 경우, 인스턴스가 null인지 확인하는 부분을 여러 스레드에서 동시에 접근한다면 인스턴스가 여러개 생성될 수 있습니다.

synchronized

synchronized 키워드를 사용해 동기화 문제를 해결할 수는 있지만 성능상 문제가 발생합니다. synchronized는 클래스 자체에 락을 걸기 때문에 getInstance()를 사용하고 있는 동안 다른 스레드들은 해당 클래스를 사용할 수 없게 됩니다.

public class MySingleton {

    private static MySingleton INSTANCE;

    private MySingleton() {
    }
    
    public static synchronized MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }
        return INSTANCE;
    }
}

Double Checked Locking

초기화 되지 않은 경우에만 동기화를 수행하므로써 synchronized의 성능 문제를 완화할 수 있습니다.

하지만 이 방식의 경우 스레드 별 CPU 캐시와 메인 메모리 간의 동기화를 보장하지 않기 때문에 volatile 키워드를 추가적으로 사용해줘야합니다.

스레드1에서 인스턴스를 생성한 후 스레드2가 synchronized 블록 안에서 null 체크를 하는 경우, 스레드1에서 생성한 인스턴스가 스레드2의 CPU 캐시에는 적용되지 않아 또 인스턴스를 생성하게 될 수 있습니다. volatile 키워드를 사용하면 인스턴스 변수를 캐시가 아닌 메인 메모리의 데이터를 직접 사용하게 하므로써 해당 문제를 방지할 수 있습니다.

public class MySingleton {

    private volatile static MySingleton INSTANCE;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (MySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new MySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

지연 초기화 홀더 클래스

이펙티브 자바 Item83. 지연 초기화는 신중히 사용하라

getInstance()를 호출하면 LazyHolder 클래스가 초기화되면서 INSTANCE 필드에 접근 가능해지기 때문에 동기화 처리가 필요 없어 성능 문제가 발생하지 않습니다.

  • JVM은 클래스 초기화 과정에서 원자성을 보장합니다.
public class MySingleton {

    private MySingleton() {
    }
    
    private static class LazyHolder() {
        private static final MySingleton INSTANCE = new MySingleton();
    }

    public static synchronized MySingleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

Java를 이용한 서버 환경에서는 싱글톤이 보장 되지 않을 수 있다.

  • private 생성자를 사용하더라도, 리플렉션을 통해 새로운 객체를 생성할 수 있습니다.
  • 서버가 여러 개의 JVM에 분산돼서 설치되는 경우, 각 JVM 마다 독립적으로 객체가 생되기 때문에 싱글톤의 장점이 퇴색됩니다.

Singleton Registry

스프링은 기본적으로 내부에서 생성하는 빈을 모두 싱글톤으로 만듭니다. (= 빈 스코프가 싱글톤)

서버에서 요청이 들어올 때마다 항상 새로운 객체를 생성해 처리하게되면 서버 부하를 일으킬 수 있습니다. 하지만 싱글톤 빈을 사용하게 되면 객체를 재사용하기 때문에 성능상 이점을 얻을 수 있습니다.

스프링은 싱글톤 패턴을 이용하지 않고, 싱글톤 레지스트리를 이용해 싱글톤을 구현합니다.

스프링에서는 빈의 제어권을 IoC 컨테이너가 가지고 있습니다. 따라서 빈의 객체 생성을 IoC 컨테이너에서 관리하기 때문에 평범한 public 생성자를 가진 자바 클래스를 싱글톤으로 만들어 관리할 수 있습니다.

  • private 생성자, static 메서드 등을 사용하지 않는 평범함 클래스를 싱글톤으로 활용할 수 있게 합니다.
  • 자유롭게 객체를 생성할 수 있기 때문에 테스트를 편하게 합니다.
  • 싱글톤 패턴과 달린 객체 지향적인 설계 방식과 다른 디자인 패턴 등을 적용하는 데 제약이 없습니다.

참고 자료

💭 크루들의 질문

synchronized의 락이 잡히는 범위는 어떻게 되나요?

  • synchronized는 다음의 4가지 방식으로 사용할 수 있습니다.
  1. synchronized method, synchronized block
    • 인스턴스 단위로 락을 건다.
    • 동일 인스턴스 내에서 synchronized가 적용된 곳과 락을 공유한다.
  2. static synchronized method, static synchronized block
    • 클래스 단위로 락을 건다.
    • synchronized method가 함께 있는 경우 인스턴스 락과 클래스 락은 서로 공유되지 않는다.