안녕하세요~

스프링 싱글톤 패턴을 공부하기 위해 선행하는 'Singleton Pattern in Java' 포스팅입니다.

 

 

 

객체 지향 프로그래밍에서, 싱글톤 클래스란 한 번에 하나의 객체만 가질 수 있는 클래스를 말합니다.

싱글톤 클래스를 인스턴스화하면 새 변수도 생성 된 첫 번째 인스턴스를 가리키게 됩니다.

 

따라서, 인스턴스를 통해 클래스 내부의 변수를 수정하면 생성 된 단일 인스턴스의 변수에 영향을 미치며

정의 된 해당 클래스 유형의 변수를 통해 해당 변수에 엑세스할 수 있습니다.

 

 

[ To design a singleton class ]

 

1. private로 생성자 만들기

  • 생성자가 public이면 외부 클래스에서 인스턴스를 여러 개 생성할 수 있다.
  • 생성자를 반드시 명시적으로 만들고 private으로 지정하여 컴파일러가 디폴트로 생성자를 만들지 않게 한다.

 

2. 해당 싱글톤 클래스 객체를 리턴하는 static method 생성

  • 인스턴스를 생성하지 않고 클래스 이름으로 참조하여 사용하기 위해 static으로 생성한다.

 

생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고,

최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴하는 것입니다.

 

 

 

 

소스 코드를 통해 확인해 보겠습니다.

 

 

Lazy Init 늦은 초기화 방식

 

클래스 로딩 시점이 아닌 인스턴스를 요청할 때마다 생성하는 방법

사용 시점 전까지 메모리를 점유하지 않습니다.

// Java program implementing Singleton class
// with getInstance() method
class Singleton
{
	// static variable single_instance of type Singleton
	private static Singleton single_instance = null;

	// variable of type String
	public String s;

	// private constructor restricted to this class itself
	private Singleton()
	{
		s = "Hello I am a string part of Singleton class";
	}

	// static method to create instance of Singleton class
	public static Singleton getInstance()
	{
		if (single_instance == null)
			single_instance = new Singleton();

		return single_instance;
	}
}

// Driver Class
class Main
{
	public static void main(String args[])
	{
		// instantiating Singleton class with variable x
		Singleton x = Singleton.getInstance();

		// instantiating Singleton class with variable y
		Singleton y = Singleton.getInstance();

		// instantiating Singleton class with variable z
		Singleton z = Singleton.getInstance();

		// changing variable of instance x
		x.s = (x.s).toUpperCase();

		System.out.println("String from x is " + x.s);
		System.out.println("String from y is " + y.s);
		System.out.println("String from z is " + z.s);
		System.out.println("\n");

		// changing variable of instance z
		z.s = (z.s).toLowerCase();

		System.out.println("String from x is " + x.s);
		System.out.println("String from y is " + y.s);
		System.out.println("String from z is " + z.s);
	}
}

 

 

 

싱글톤 클래스를 인스턴스화하면 새 변수도 생성 된 첫 번째 인스턴스를 가리키며,

인스턴스를 통해 클래스 내부의 변수를 수정할 수 있다는 것을 확인할 수 있습니다.

 

다만 위와 같이 싱글톤 패턴을 구현하면

멀티 스레드 환경에서 특정 스레드가 동시에 getInstance 호출 시 인스턴스가 2개 이상 생길 가능성이 있습니다.

 

 

 

 

현재 Java 싱글톤을 구현하는 대표적인 방법은 아래와 같습니다.

 

Holder 클래스 안의 클래스

 

JVM의 Class Loader 매커니즘과 Class가 로드되는 시점을 이용한 방법으로

Lazy init 방식의 장점을 가져가면서 스레드 간 동기화 문제를 해결할 수 있습니다. 

JVM 자체의 특성을 최대한 이끌어내어 성능저하를 막는 방식입니다.

 

미국 메릴랜드 대학의 컴퓨터 과학 연구원 Bill pugh가 제안한 Initialization on demand holder idiom 기법이라고 합니다.

 

class ExampleClass {

    //private construct
    private ExampleClass() {}

    private static class HolderSingleton {
        private static final ExampleClass instance = new ExampleClass();
    }

    public static ExampleClass getInstance() {
        return HolderSingleton.instance;
    }
}

 

ExampleClass는 클래스 로드 시점에 초기화되지만 정적 클래스로 정의 된 내부 클래스의 초기화는 해당 시점에 이뤄지지 않습니다.

 

중첩클래스 HolderSingleton은 getInstance 메서드가 호출되기 전에는 참조 되지 않으며,

최초로 getInstance() 메서드가 호출 될 때 클래스 로더에 의해 싱글톤 객체를 생성하여 리턴합니다.

 

HolderSingleton 안에 선언된 instance가

static 이기 때문에, 클래스 로딩 시점에 한번만 호출되며

final 이므로, 다시 값이 할당되지 않습니다.

 

또한, 이 방식이 thread safe 한 이유는 JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용하기 때문입니다.

JVM 의 클래스 초기화 과정이 sequential, non-concurrent 하므로

즉, JVM이 클래스를 초기화하면서 중복 인스턴스 생성하지 않는 역할을 하도록 구현한 것입니다.

 

결론적으로

instance는 getInstance() 호출 시 HolderSingleton 클래스의 초기화가 이루어 지면서 원자성이 보장된 상태로 단 한번 생성되고,

final 변수 이므로 이후로 다시 instance 가 할당되는 것을 막을 수 있습니다.

 

이러한 방법의 장점은 Synchronzied 를 사용하지 않아도 JVM 자체가 보장하는 원자성을 사용하여 Thread-Safe 하게 싱글톤 패턴을 구현할 수 있다는 것 입니다.

 

 

참고) 자바와 Spring에서 싱글톤 객체의 생명주기가 다르며

자바에서 공유 범위는 Class loader 기준이지만, Spring에서는 ApplicationContext가 기준이 된다고 합니다.

 

위 포스팅 예제 코드는 아래를 참조하였습니다.

 

www.geeksforgeeks.org/singleton-class-java/

blog.seotory.com/post/2016/03/java-singleton-pattern

en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

 

참고하면 좋을 포스팅

 

djkeh.github.io/articles/Why-should-final-member-variables-be-conventionally-static-in-Java-kor/

복사했습니다!