본문 바로가기

study/effective java

[item3] 싱글톤을 만들 시 private 생성자나 enum type으로 구현해라.

이 item의 내용들은 java를 싱글톤으로 만드는 좋은 방법에 대한 제안이다.

싱글톤 패턴이란

싱글톤은 오직 1개의 인스턴스를 여러 객체가 공유하여 사용하는 패턴이다. 이 패턴은 자원을 많이 사용하는 객체일 경우 하나의 단일 인스턴스를 만들어 공유하므로서 자원을 아끼기 위해 사용한다.

싱글톤 인스턴스는 단일 스레드를 이용할 때는 구현이 쉬우나 멀티 스레드 환경에서는 어려워질 수 있다.

1. private 생성자를 통한 단일 인스턴스 관리

private 생성자로 인스턴스를 막아놓고 정적 팩토리 메소드를 통해 인스턴스로 접근하는 방법이다.

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
        // Singleton instance 초기화 작업 수행
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

    public void doSomething() {
        // Singleton instance를 사용하여 작업 수행
    }
}

singleton 을 만드는 가장 간단한 방법이다. 이 방식의 장점은 편리하지만 2가지의 문제점이 있다.

  1. 직렬화 후 역직렬화를 통한 인스턴스 복제
    • 인스턴스를 직렬화 한 후에 역직렬화를 하면 값은 같지만 주소가 다른 새로운 인스턴스가 만들어진다.
  2. reflection을 이용한 private 생성자 접근
    • reflection이란 객체의 정보를 런타임에서 분석하는 방법이다. 접근제한자에 제약 없이 내부에 대한 접근을 하고 메소드를 임의로 호출하거나 변수값을 임의로 바꿀 수 있다.
    • private 메소드로 막아놓아도 reflection을 이용해 private 생성자를 접근하여 새로운 생성자를 만들 수 있다.

1번의 단점을 해결하는 방법은 쉽다. 바로 readResolve 메소드를 오버라이딩하는 것이다. 이 함수는 직렬화된 데이터를 역직렬화 하여 객체를 만들 때 호출되는 메소드이다. 이 메소드를 오버라이딩하여 역직렬화를 하여도 새로운 객체가 아닌 기존에 만들어놓은 인스턴스를 반환하도록 작성한다.

import java.io.Serializable;

public class Singleton implements Serializable {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
    
    private Object readResolve() {
        // 항상 Singleton 객체의 인스턴스를 반환
        return INSTANCE;
    }
}

그러나 이 방법을 사용해도 2번의 단점을 해결할 수 없다.

2. enum를 이용한 싱글톤 인스턴스 관리

enum type를 통해 싱글톤 인스턴스를 만드는 것이다. enum type은 다음의 특징을 가지고 있다.

  1. reflection을 통한 접근이 불가능하다.
  2. 직렬화, 역직렬화 코드가 내장되어 있다.
  3. JVM이 컴파일 타임에서 만들어준다.

즉 enum type를 이용시 private 생성자를 이용한 단점들이 모두 해결이 된다. 아래의 코드는 enum type를 이용한

public enum SingletonEnum {
    INSTANCE;

    private static class SingletonInstance {
        private static final SingletonEnum INSTANCE = new SingletonEnum();
    }

    public static SingletonEnum getInstance() {
        return SingletonInstance.INSTANCE;
    }

    public void doSomething() {
        // Singleton 객체의 동작
    }
}

이 방식의 문제점은 enum type이 컴파일 타임에서 생성이 되는 문제가 있다. singleton 인스턴스가 java 프로그램이 실행되는 직후부터 메모리에 상주하기 때문에 공간 낭비가 발생할 수 있다. 즉 메모리에 사용하지도 않는 singleton 인스턴스가 계속 상주하여 공간을 낭비한다. private 생성자를 이용한 방법은 생성자가 호출되는 시점에서 새로 인스턴스를 만들도록 제어할 수 있다. 즉 항상 메모리에 상주시킬 필요가 없다.

결론

본 내용은 singleton 의 인스턴스가 1개만 유지하도록 하는 java만의 기법을 다룬 내용이다. 그렇다 하더라도 본질은 싱글톤이다. private으로 생성하든 enum으로 생성하든 이 방법의 장단점이 무엇이 있으며 싱글톤이 만들어질 컴퓨터의 자원환경에 따라 어떻게 구현할 것인지를 고민해봐야 한다.


Reference