• All Objects
    • Life
    • Technic
    • entry
  • Tags
  • wsl2
  • git
  • go
  • github
  • docker
Technic

Singleton Pattern

2020. 8. 4. 23:51

애플리케이션을 구동하면서 보통은 많은 인스턴스를 만들지만, 1개만 필요한 경우가 있습니다. 애플리케이션 전체에서 동일한 설정 값을 사용해야 한다던지, DB 스레드 풀, 캐시와 같이 애플리케이션 전체에서 공유하여 사용하는 리소스라던지, 로그를 기록하는 객체 같은 것들은 애플리케이션에서 1개의 인스턴스만 생성되어야 합니다. 

 

이렇게 한 개의 인스턴스만 존재함을 보증하는 패턴을 Singleton 패턴이라고 부릅니다. 

 

클래스 다이어그램

Singleton 패턴의 클래스 다이어그램은 다음과 같이 표현합니다. 

singleton 속성과 Singleton 생성자가 모두 private으로 정의되어 있습니다. 이는 생성된 객체를 직접 접근하거나 생성할 수 없도록 명시하는 것입니다. 생성된 인스턴스는 getInstance 메서드를 통해서 접근할 수 있게 됩니다. 그리고 singleton 속성과 getInstance 메서드에는 밑줄이 그어져 static 임을 표시하였습니다. 

 

코드

위 클래스 다이어그램으로 코드를 작성하면 다음과 같습니다. 

public class Singleton {
    private static Singleton singleton;
    private Singleton(){
        System.out.println("Singleton Instance Created");
    }

    public static Singleton getInstance() {
        if( singleton == null ){
            singleton = new Singleton();
        }
        return singleton;
    }
}

singleton 객체가 생성되지 않은 경우 새로 객체를 생성하여 리턴하고, 이미 생성된 경우, 이미 생성된 객체를 리턴합니다. 이 방법으로 구현하는게 일반적이지만, 멀티스레드 환경에서는 적합하지 않습니다. 만약 singleton객체가 생성되는 도중, getInstance가 다시 호출된다면, singleton 객체가 생성되기 전이므로 새로운, singleton 객체를 생성하여 리턴하게 되므로, 한 개의 객체만이 생성됨을 보장할 수 없습니다. 이를 다른 말로 스레드에 안전하지 않다고 합니다. ( thread-safe)

 

이 방법의 해결책으로는 Singleton 객체가 필요할 때 인스턴스를 생성하지 않고, 아예 클래스 로딩 당시에 인스턴스를 생성하는 것입니다. 일반적으로 클래스로딩은 스레드에 안전함을 보장하므로 위와 같은 문제는 발생하지 않게 됩니다. 그래서 Singleton 클래스가 로딩될 때 인스턴스가 생성되도록 변경하면 다음과 같습니다. 

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){
        System.out.println("Singleton Instance Created");
    }

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

스레드에 안전하게 변경되었습니다. 하지만, 이 경우도 문제가 하나 있습니다. 사용하지 않더라도 인스턴스를 생성하기 때문에 자원을 낭비하게 됩니다. 항상 사용하는 인스턴스라면 문제가 되지 않겠지만, 인스턴스가 차지하는 메모리도 크고, 사용빈도도 낮다고 하면, 불필요한 자원을 낭비하는 꼴이 됩니다. 

 

그래서 Singleton 객체의 인스턴스를 나중에 생성하기 Signle객체를 담는 클래스를 만들고 그 클래스의 로딩을 사용시에 하도록 만들어 줍니다. 

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){
        System.out.println("Singleton Instance Created");
    }

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

    private static class LazyHolder {
        private static final Singleton instance = new Singleton();
    }
}

위에서 클래스를 로딩하는 시점은 스레드에 안전함을 보장한다고 하였으므로, synchronized 같은 키워드 없이도 멀티 스레드 프로그램에서도 문제 되지 않고, getInstance가 호출되기 전까지는 클래스가 로딩되지 않기 때문에 시스템 자원을 낭비하는 문제도 없게 됩니다. 그래서 이 방법이 현재까지 가장 완벽하다고 알려져 있습니다. 

 

그 외에도 synchronized 키워드를 사용하여 구현하는 방법도 있지만, synchronized 키워드를 사용하면, 성능이 떨어지게 되므로, 요새는 잘 사용하지 않기에 별도로 언급하지 않았습니다. 

반응형
copyright 2020. noname

티스토리툴바