심심해서 하는 블로그 :: 'Computer Science/디자인 패턴' 카테고리의 글 목록

'Computer Science/디자인 패턴'에 해당되는 글 1건

Singleton

싱글톤이란?

하나! 오직 하나!


싱글톤 패턴은 클래스의 인스턴스가 오직 하나임을 보장하는 디자인 패턴이다.

어떤 클래스의 인스턴스를 생성하고 소멸하는 과정이 빈번하게 발생한다면, 고민해도 될 만한 패턴


대표적인 형태 - Eager initialization

public class Singleton{
	// 인스턴스를 Static 생성한다. 
	private static final Singleton instance = new Singleton();

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 인스턴스를 사용할 때는 이렇게 호출한다.
	public static Singleton getInstance(){
		return instance;
	}
}


싱글톤 패턴을 사용하는 코드에서 나타나는 특징은 크게 세 가지 입니다.


  1. private로 선언된 생성자

단 하나의 인스턴스를 보장하기 위해서, 클래스 외부에서 새로운 인스턴스의 생성을 막아야 하므로 private로 접근을 제어한다.


  1. Static으로 선언된 인스턴스 멤버 변수

일반적으로 우리가 new 를 사용하여 클래스의 인스턴스를 선언하는 방법을 동적 생성이라고 하는데, 이 때는 메모리 구조상 Heap에 위치하게 된다.

Heap에 있는 인스턴스는 메모리 할당과 해제를 거치게 되는데, 이 연산이 많은 사용자들이 사용하는 서버에서 동시에 이루어 진다면, 부하가 될 수 있다.

반면 Static으로 선언된 인스턴스는 최초 클래스 로드시에 메모리에 적재된 후, 계속 메모리에 남게 된다.


  1. 오직 getInstance() 함수로 Instence 접근

이 클래스의 인스턴스를 접근할 수 있는 곳은 오로지 getInstance() 입니다.


위의 세 가지 조건을 만족한다면 클래스의 인스턴스가 1개인 클래스를 생성가능합니다.

이 방법이 가지는 단점은 이 클래스를 사용하든 안하든 항상 메모리 상에 상주한다는 겁니다.

getInstance() 가 실행 되기 전부터 메모리에 상주하여 낭비를 한다는 것이 마음에 썩 들지는 않습니다.

그래서 클래스를 사용할 때 메모리에 인스턴스를 로드하는 방법을 고안합니다.





Lazy Initialization

public class Singleton{
	// 미리 생성하지 않는다.
	// private static final Singleton instance = new Singleton();
	private static Singleton instance = null;

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 인스턴스를 사용할 때는 이렇게 호출한다.
	public static Singleton getInstance(){
		// 추가!! 최초로 사용할 때 인스턴스를 생성한다.
		if (instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}


이전 코드와 비교 하기 위해서 주석으로 이전 코드를 남겨 두었다.


  1. 미리 생성하지 않는다.

클래스의 인스턴스를 사용하기 전부터 메모리에 상주하던 방법이 아닌 최초로 사용하는 곳에서 인스턴스를 생성합니다.

getInstance() 함수가 한 번이라도 실행되어야 이 클래스를


  1. 단 하나의 Instance 보장 문제

우연히 같은 시간에 여럭 개의 쓰레드가 getInstance()를 호출된다면, 하나의 인스턴스를

이 경우에 단 하나의 Instance를 보장 가능하다고 말 할 수 없습니다.

아래의 코드는 Thread-Safe 를 붕괴해보는(?) 소스코드 입니다.


import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Runner implements Runnable{
   public static final CyclicBarrier gate = new CyclicBarrier(20);

   @Override
   public void run() {
      try {
         gate.await();
         Singleton s = Singleton.getInstance();
      } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
      }
   }

   public static void main(String[] args) {      
      for(int i=0; i<20; i++) {
         Runner broker = new Runner();
         Thread worker = new Thread(broker);
         worker.start();
      }
   }
}


이 소스 코드는 20개의 쓰레드가 동시에 getInstance() 를 실행하는 소스 코드입니다.

실행 결과를 보면 여러 개의 인스턴스가 생성되는 것을 확인 할 수 있습니다.



Lock을 걸어버리자

public class Singleton{
	private static Singleton instance = null;

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 추가!! Lock을 걸어보자
	public static synchronized Singleton getInstance(){
	// public static Singleton getInstance(){
		if (instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}


synchronized 로 선언된 영역은 Lock을 걸어 주는 역할을 수행하므로 하나의 쓰레드씩 이 함수를 수행하게 되므로 Thread-Safe 문제를 보완해 줍니다.

하지만 우리가 병렬로 수행하기 위한 목적으로 멀티쓰레드를 쓰는데, synchronized 구간에서 병렬성이 무너지기 때문에 성능저하가 발생할 수 있습니다.






holder에 의한 초기화

public class Singleton{
	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}
	
	// Holder Class 추가
	private static class Holder {
		static{
			System.out.println("Holder Class");
		} // Holder 클래스를 메모리에 로드할 때 메세지를 보여주기 위함
		private static final Singleton instance = new Singleton();
	} 	

	public static Singleton getInstance(){
		return Holder.instance;
	}
	// getInstance() 말고 다른 함수를 호출하였을 때 인스턴스 생성 여부 확인용
	public static void print(){
		System.out.println("Singleton.print()");
	}
}


Singleton 클래스 내부에 Holder 클래스를 작성하고 그의 멤버 변수로 Singleton 클래스의 인스턴스를 생성합니다.

앞서 소개한 두 가지 방법에서 발생한 문제점을 어느 정도 보완하는지 체크해 보았습니다.


  1. 사용 전에 미리 생성하지 말 것

getInstance() 호출 전 Singleton 클래스의 인스턴스가 static 맴버 변수로 작성되지 않았으므로 인스턴스가 생성되지 않습니다.

위의 소스코드에서getInstance() 를 실행하지 않고 print() 함수만 실행했을 때 결과를 보면 쉽게 이해하실 수 있을 겁니다.


  1. Thread-Safe 보장

getInstance() 함수가 실행되면 Static으로 선언된 Holder 클래스는 JVM의 Class Loader 에 의해 메모리에 적재됩니다.

Holder는 Singleton 클래스의 인스턴스를 가지고 있으니 Thread-Safe를 붕괴할 수 있는 경우는 Holder 클래스가 Class Loader에 의해 메모리에 적재될 때 중첩되어 Load되는 경우 뿐입니다.

하지만 Class가 초기화되는 과정은 Java Language Specification (JLS)에 의해 시퀀스하게 진행됩니다.

그래서 synchronized로 실행했을 때처럼 Lock을 제공하지만 더 좋은 성능을 보장하는 방법을 제공합니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기부여가 됩니다.


,