본문 바로가기
CS/Design Pattern

자바 싱글톤 패턴의 변화 (다양한 싱글톤 패턴 구현 방법)

by Nahwasa 2022. 11. 15.

목차

     

      싱글톤 패턴은 어떠한 클래스에 대한 객체(=인스턴스)를 해당 프로그램에서 단 하나만 가지도록 강제하는 구현 패턴 입니다. 단 하나만 있지 않으면 문제가 생기거나, 하나만 있어도 문제가 없을 때 사용하면 됩니다.

     

      자바로 싱글톤 패턴을 구현하는 방법들을 점차 이전의 단점을 해결하면서 변화하는 코드들로 짧게 얘기해보려 합니다. 주관적 견해도 들어가 있으므로 정답은 아닙니다. 

     

    1. 가장 기초가 되는 구현 방법

    class ConnectionManager {
        private final static ConnectionManager instance = new ConnectionManager();
    
        private ConnectionManager() {}
    
        public static ConnectionManager getInstance() {
            return instance;
        }
    }

      private 생성자로 다른 곳에서 new로 생성되는 것을 막습니다. 자바 실행 시 instance가 생성되고 이후 다른 곳에서 getInstance()로 하나만 생성되어 있는 instance를 받아 사용합니다.

     

      이 경우 사용하지 않을 생각이더라도, 실행 시 instance에 객체가 생성되어 있다는 단점이 있습니다. 예를들어 DB 연결과 관련된 싱글톤 클래스인데, DB를 사용하지 않더라도 항상 메모리에 올라와 있게 됩니다.

     

     

    2. 사용하려고 할 때 생성하자

    class ConnectionManager {
        private static ConnectionManager instance = null;
    
        private ConnectionManager() {}
    
        public static ConnectionManager getInstance() {
            if (instance == null)
                instance = new ConnectionManager();
            return instance;
        }
    }

      '1'의 instance를 null로 두고 있다가, getInstance()를 통해 누군가 사용하려 할 때 instance가 null일 때만 한 번 객체를 생성해줍니다. 이후 하나만 생성된 instance를 리턴해주는 방식입니다.

     

      싱글 스레드라면 문제가 없으나, 멀티 스레드 환경에서 Thread safe를 만족하지 못합니다. 우연히 근접한 시간에 두 스레드가 getInstance()를 호출할 경우 new 가 2번 호출될 수 있습니다.

     

     

    3. synchronized로 해결해보자

    class ConnectionManager {
        private static ConnectionManager instance = null;
    
        private ConnectionManager() {}
    
        public static synchronized ConnectionManager getInstance() {
            if (instance == null)
                instance = new ConnectionManager();
            return instance;
        }
    }

      getInstance() 함수에 synchronized를 붙이면 한 스레드에서 접근 시 다른 스레드의 접근을 막을 수 있어서 Thread safe를 만족하게 됩니다.

     

      다만 synchronized가 getInstance()를 호출할 때 마다 수행되므로 동작 시간이 오래걸립니다. 멀티 스레드에 대해 동기화 시키는 부분은 어느 언어나 흔히 말하는 'expensive'한 연산입니다. 물론 여기 하나 붙였다고 그렇게 유의미하게 차이가 나냐고 하면 또 그렇진 않은데, 이후 더 좋은 방법이 있긴 하니 넘어가겠습니다! 

     

     

    4. 객체 생성시에만 synchronized를 적용시켜 보자

    class ConnectionManager {
        private static ConnectionManager instance = null;
    
        private ConnectionManager() {}
    
        public static ConnectionManager getInstance() {
            if (instance == null) {
                synchronized (ConnectionManager.class) {
                    if (instance == null) {
                        instance = new ConnectionManager();
                    }
                }
            }
            return instance;
        }
    }

      synchronized를 getInstance() 함수 자체에 걸지 않고 안쪽으로 가져갔습니다. 그렇다면 멀티스레드 환경에서 우연히 충돌이 났을 경우에만 synchronized를 적용할 수 있으므로 '3'에서의 성능 문제도 해결되었습니다.

     

      다만 Serializable을 implements한 경우 역직렬화 시 새로운 객체가 생성될 수 있고, 리플렉션을 통한 공격에 취약하다.

     

     

    5. enum을 사용하자

    enum ConnectionManager {
        INSTANCE;
    }

      직렬화 및 리플렉션 문제를 해결하기 위해 enum을 싱글톤 객체처럼 사용할 수도 있다. 이 경우 1~4의 모든 문제가 해결된다.

     

      다만 enum의 원래 enum의 사용법에 맞지 않다. 개인적으로 설계 후 클래스 다이어그램만 봐도 어느정도 동작이 이해되야 한다고 생각한다. enum을 싱글톤처럼 사용한 부분은 별도로 설명하지 않는 이상 이해할 수 없다.

     

     

    6. 내부 클래스를 통해 구현해보자

    class ConnectionManager {
        private ConnectionManager() {}
    
        private static class ConnectionManagerHolder {
            private static final ConnectionManager instance = new ConnectionManager();
        }
    
        public static ConnectionManager getInstance() {
            return ConnectionManagerHolder.instance;
        }
    }

      '4'의 경우 코드가 이쁘지 않은 것 같다. 내부 클래스를 통해서도 싱글톤 구현이 가능하다. 내부 클래스에 있는 필드값의 경우 프로그램 실행 단계에서 생성되지 않으므로, 누군가 getIntacne()를 호출할 때 생성되게 된다. 또한 멀티 스레드에 대한 동기화는 JVM이 보증해준다.

     

      다만 '5'에서는 해결된 직렬화, 리플렉션이 해결되진 않는다. 그래도 enum을 사용해 용법에 맞지 않는걸 사용하는 것 보다는 개인적으로 낫다고 생각한다. 직렬화 및 리플렉션 문제도 사실 짜고 있는 프로그램에서 문제가 될 것 같다면 별도의 방어코드를 넣어 해결 가능하다.

     

     

    References

    1. 면접을 위한 CS 전공지식 노트 (주홍철 저)

     

    2. 이펙티브 자바 Effective Java 3/E (조슈아 블로크 저)

     

    3. https://dzone.com/articles/another-singleton-implementation

     

    Another Singleton Implementation - DZone Java

     

    dzone.com

     

    4.https://www.digitalocean.com/community/tutorials/java-singleton-design-pattern-best-practices-examples

     

    Java Singleton Design Pattern Best Practices with Examples | DigitalOcean

     

    www.digitalocean.com

    댓글