Singleton vs Static Method

한강섭·2025년 4월 1일
0
post-thumbnail

썸네일 출처


Singleton Pattern


정의

소프트웨어 디자인 패턴에서 싱글턴 패턴(Singleton pattern)을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글턴 패턴이라고 한다.


쓰는 이유

  1. 메모리 절약을 위해
  2. 싱글톤 클래스의 인스턴스는 전역이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉬움
  3. 대표적으로 데이터베이스 연결 모듈을 예로 들 수 있는데, 데이터베이스에 접속하는 작업(I/O 바운드)은 그 자체로 무거운 작업에 속하며 또한 한번만 객체를 생성하고 돌려쓰면 되지 굳이 여러번 생성할 필요가 없다
  4. 그 밖에도 디스크 연결, 네트워크 통신, DBCP 커넥션풀, 스레드풀, 캐시, 로그 기록 객체 등에 이용

싱글톤 패턴 7가지


  1. Eager Initialization
class Singleton {
    // 싱글톤 클래스 객체를 담을 인스턴스 변수
    private static final Singleton INSTANCE = new Singleton();

    // 생성자를 private로 선언 (외부에서 new 사용 X)
    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

static final이라 멀티 쓰레드 환경에서도 안전
BUT static member는 객체를 사용하지 않더라도 메모리에 적재되기 때문에 공간 자원 낭비 발생
AND 예외 처리 불가!


  1. Static block initialization
class Singleton {
    // 싱글톤 클래스 객체를 담을 인스턴스 변수
    private static Singleton instance;

    // 생성자를 private로 선언 (외부에서 new 사용 X)
    private Singleton() {}
    
    // static 블록을 이용해 예외 처리
    static {
        try {
            instance = new Singleton();
        } catch (Exception e) {
            throw new RuntimeException("싱글톤 객체 생성 오류");
        }
    }

    public static Singleton getInstance() {
        return instance;
    }
}

예외는 처리 되지만 여전히 static으로 선언되어 있어 공간 차지!


  1. Lazy initialization
class Singleton {
    // 싱글톤 클래스 객체를 담을 인스턴스 변수
    private static Singleton instance;

    // 생성자를 private로 선언 (외부에서 new 사용 X)
    private Singleton() {}
	
    // 외부에서 정적 메서드를 호출하면 그제서야 초기화 진행 (lazy)
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton(); // 오직 1개의 객체만 생성
        }
        return instance;
    }
}

고정 메모리 차지를 없앰!

BUT 쓰레드 세이프 하지 않는 치명적인 단점을 가지고 있음


  1. Thread safe initialization
class Singleton {
    private static Singleton instance;

    private Singleton() {}

    // synchronized 메서드
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

synchronized 를 통해 하나의 쓰레드만 통과할 수 있도록 만듬!

BUT 성능이 너무 떨어진다!


  1. Double-Checked Locking
class Singleton {
    private static volatile Singleton instance; // volatile 키워드 적용

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
        	// 메서드에 동기화 거는게 아닌, Singleton 클래스 자체를 동기화 걸어버림
            synchronized (Singleton.class) { 
                if(instance == null) { 
                    instance = new Singleton(); // 최초 초기화만 동기화 작업이 일어나서 리소스 낭비를 최소화
                }
            }
        }
        return instance; // 최초 초기화가 되면 앞으로 생성된 인스턴스만 반환
    }
}

매번 synchronized 동기화를 실행하는 것이 문제라면, 최초 초기화할때만 적용하고 이미 만들어진 인스턴스를 반환할때는 그냥 반환함!

이때 인스턴스 필드에 volatile 키워드를 붙여주어서 I/O 불일치 문제를 해결함

BUT JVM에 따라서 쓰레드 세이프 하지 않는 경우가 있을수도 있음


  1. Bill Pugh Solution (LazyHolder) 👌(권장)
class Singleton {

    private Singleton() {}

    // static 내부 클래스를 이용
    // Holder로 만들어, 클래스가 메모리에 로드되지 않고 getInstance 메서드가 호출되어야 로드됨
    private static class SingleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingleInstanceHolder.INSTANCE;
    }
}

멀티쓰레드 환경에서도 안전하고 Lazy Loading도 가능한 완벽한 싱글톤

but 클라이언트가 임의로 싱글톤을 파괴할 수 있다.


  1. Enum 이용 👌 (권장)
enum SingletonEnum {
    INSTANCE;

    private final Client dbClient;
	
    SingletonEnum() {
        dbClient = Database.getClient();
    }

    public static SingletonEnum getInstance() {
        return INSTANCE;
    }

    public Client getClient() {
        return dbClient;
    }
}

public class Main {
    public static void main(String[] args) {
        SingletonEnum singleton = SingletonEnum.getInstance();
        singleton.getClient();
    }
}

Bill Pugh Solution 기법과 달리 클라이언트에서의 공격에도 안전

BUT 싱글톤 클래스를 멀티톤(일반적인 클래스)로 마이그레이션 해야할 때 처음부터 코드를 다시 짜야함
AND 클래스 상속이 필요할 때, enum 외의 클래스 상속은 불가함


싱글톤 정리

다양한 방법이 각각의 단점을 보완하기 위해서 발전되어 왔고
만약 싱글톤 패턴 클래스를 만든다면

LazyHolder : 성능이 중요하다면
Enum : 직렬화, 안정성 이 중요하다면

이렇게 선택하여 사용하면 된다


static method


정의

정적 메서드는 클래스의 인스턴스 없이 호출이 가능한 메서드이다.

모든 메소드가 static인 클래스를 지칭,
inner static class를 뜻하기도 한다.


쓰는 이유

상태를 가지고 있지 않고 global access를 제공해야할 때 유용
static은 컴파일 할 때 static binding 으로 싱글톤보다 좀 더 빠름
클래스 자체에 static을 붙여 사용할 수 없음 (inner class일 때만 가능)


예시

public static void printValue(final String value) {
	System.out.println(value);
}

싱글턴 vs 정적 메서드


싱글턴 장점

  1. 런타임 다형성을 활용할 수 있다. (상속 사용, 확장 용이)
  2. 인터페이스를 구현할 수 있다.
  3. 객체로 존재하므로 싱글턴을 매개변수로 다른 메서드에 전달할 수 있다.

싱글턴 단점

  1. 너무 많은 일을 위임하거나 공유하는 경우 coupling이 많아지고 결합도가 높아진다.
  2. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖게 되어 안정성이 떨어진다.

정적 메서드 장점

  1. 매번 인스턴스를 생성하기 위해 생성자를 호출하지 않아도 되므로 낭비되는 메모리를 줄일 수 있다.
  2. 객체를 생성하지 않고 사용 가능하기 때문에 속도가 빠르다.

정적 메서드 단점

  1. static을 사용하게 되면 프로그램 시작부터 끝까지 메모리에 할당된다.
  2. 오버라이드가 불가능하며, 객체와 관련이 없기 때문에 절차지향적 성향이 강하다. 무분별하게 사용하면 객체 지향을 해친다.

결론

싱글턴은 단 하나의 클래스 인스턴스만 필요하고, 모든 곳에서 동일한 상태를 유지하고 싶은 경우 사용한다. 이후 클래스 확장을 통한 메서드 재정의가 필요한 경우에 사용한다. 테스트가 어려운 정적 메서드에 비해 싱글턴은 mocking 을 통한 테스트가 용이하다.

그러나 내부 상태를 변경할 필요가 없고, 각 메서드가 매개 변수에 대해서만 작동한다면 정적 메서드를 사용할 수 있다. 또한 다형성이 필요 없고 앞으로 객체 지향을 적용할 필요가 없을 때도 사용 가능하다.


참고한 자료

Singleton(싱글톤) vs Static Method (정적 메서드)
싱글톤 패턴 vs 정적 클래스 (Singleton Pattern vs Static Class)
싱글톤과 정적클래스
싱글톤(Singleton) 패턴 - 꼼꼼하게 알아보자

profile
개발😊

0개의 댓글