공부중/개념알기

[JAVA] 싱글톤 패턴

서규Dev 2022. 7. 29. 10:04

싱글톤이란?

- 어떤 클래스가 최초 한 번만 메모리를 할당하고(Static) 그 메모리에 객체를 만들어 사용하는 디자인 패턴.

즉, 생성자의 호출이 반복적으로 이루어지더라도 실제로 생성되는 객체는 최초 생성된 객체를 반환해주는 것이다.

싱글톤 패턴을 사용하는 이유는 메모리 낭비를 방지하기 위함이다.

또한 싱글톤으로 생성된 객체는 무조건 한 번 생성으로 전역성을 띄기 때문에 다른 객체와 공유가 용이하다.

단, 싱글톤의 문제점은 아래와 같다.

(1) 싱글톤으로 만들어진 객체의 역할이 간단한 것이 아닌 복잡한 경우라면, 해당 싱글톤 객체를 사용하는 다른 객체 간의 결함도가 높아져서 객체 지향 설계 원칙에 어긋나게 된다. (개방 - 폐쇄)

(2) 또한 해당 싱글톤 객체를 수정하는 경우 싱글톤 객체를 사용하는 곳에서 사이드 이팩트가 발생할 확률이 생기게 되며, 멀티 스레드 환경에서 동기화 처리 문제 등이 생기게 된다.

 


싱글톤 패턴은 Spring framework에서 많이 사용된다.

JAVA와 Spring에서의 싱글톤 차이점은 싱글톤 객체의 생명주기에 있다.

또한 JAVA에서 공유 범위는 Class Loader 기준이지만, Spring에서는 ApplicationContext가 기준이 된다.

public class ExampleClass {
	//Instance
	private static ExampleClass instance = new ExampleClass();
	//private construct
	private ExampleClass() {}
	public static ExampleClass getInstance() {
		return instance;
	}
}

위 코드에서 instance라는 전역 변수를 선언하는데 static을 줌으로써 인스턴스화 하지 않고 사용할 수 있게 하였다.

그러나 접근 제한자가 private로 되어 있어서 직접적인 접근은 불가능하다.

또한 생성자도 private로 되어있어서 new를 통한 객체 생성도 불가능하다.

결국 getInstance 메서드를 통해서 해당 인스턴스를 얻을 수 있게 된다.

이 외에도 다양한 싱글톤의 구현 방식이 있다. 아래와 같다.

public class ExampleClass {
    //Instance
    private static ExampleClass instance;
    //private construct
    private ExampleClass() {}
    static {
        try { 
        	instance = new ExampleClass();
        } catch(Exception e) { 
        	throw new RuntimeException("Create instace fail. error msg = " + e.getMessage() ); 
        }
    }
    public static ExampleClass getInstance() {
    	return instance;
    }
}

위와 같이 static 블록을 사용할 경우 로딩될 때 한 번만 실행을 하게 되는 특성을 사용한다.

단, 인스턴스가 사용되는 시점이 아닌 클래스 로딩 시점에 실행된다.

//----------[Lazy Init 형태]----------
public class ExampleClass {
    //Instance
    private static ExampleClass instance;
    //private construct
    private ExampleClass() {}
    public static ExampleClass getInstance() {
        if (instance == null) instance = new ExampleClass();
        return instance;
    }
}

 

위 static 방법에서 개선하여 클래스 로딩 시점이 아닌 인스턴스가 필요하여 요청할 때 생성되는 형태로 작성된 방법이다.

단, 위 형태로 구현할 경우 멀티 스레드 환경에서 취약하다.

특정 스레드가 동시에 getInstance() 메서드를 호출하게 되면 인스턴스가 두 번 생성되는 문제가 발생한다.

public class ExampleClass {
    //Instance
    private static ExampleClass instance;
    //private construct
    private ExampleClass() {}
    public static ExampleClass getInstance() {
        if (instance == null) instance = new ExampleClass();
        return instance;
    }
}

Lazy에서 보였던 getInstance() 메서드에 synchronized 키워드를 붙임으로써 스레드에서 동시 접근에 대한 문제를 해결한 방법이다.

단, synchronized 키워드는 성능 저하를 발생시킨다.

//----------[Holder형태]----------
public class ExampleClass {
    //private construct
    private ExampleClass() {}
    private static class InnerInstanceClazz() {
    	private static final ExampleClass instance = new ExampleClass();
    }
    public static ExampleClass getInstance() {
   		return InnerInstanceClazz.instance;
    }
}

JVM의 클래스 로더 메커니즘과 클래스 로드 시점을 이용하여 내부 클래스를 통해 생성시킴으로써 스레드 간의 동기화 문제를 해결한다.

위 방법은 현재 JAVA에서 싱글톤 생성 방법 중 가장 대표적인 방법이다.