고전적인 싱글턴 패턴

- 객체가 하나만 필요한 경우는 굉장히 많다, 스레드풀, 레지스트리 설정 객체 아니면 그래픽카드객체 등등... 이런경우에는 객체가 한가지만 필요하다.

- 분명히 static 전역 객체를 쓰면, 구지 싱글턴을 안써도 될텐데....? 라는 말을 할 수 있을것이다. 그렇지만 메모리 효율이라는 명목아래 우리는 싱글턴 패턴을 쓴다.

public class Singleton {
	private static Singleton unique;
    
    //접근 제한자를 써서 외부에서 객체를 생성못하게함.
    private Singleton() {}
    
    public static Singleton getInstance () {
    	if (unique == null) {
        	uniqueInstance = new Singleton();
        }
        return unique;
    }

}

 

- 위와같은 코드는 누구나 한번쯤 봤을것이다. 그러나, 이런 식으로 싱글턴을 쓰지않도록 주의해야 한다.

 

문제 제기

- 우리는 초콜릿바 공장을 운영하고 있고, 초콜릿을 녹이는 가마솥이 있다고 가정하자, 이 솥은 컴퓨터로 제어된다.

- 이 가마솥에서는 초콜릿과 우유를 받아서 끓이고 다음공정으로 넘기는 역할을 한다. 빈 가마솥을 끓이다거나 가득찬 가마솥에 끓인다거나 하는 실수는 절대 하지말아야한다.

public class ChocolateGamasot {
	private boolean empty;
	private boolean heat;
	
	public ChocolateGamasot() {
		empty = true;
		heat = false;
	}
	
	//가마솟이 비어있을때만 집어넣음.
	public void fill() {
		if(isEmpty()) {
			empty = false;
			heat = false;
			// 원유를 집어넣음
		}
	}
	
	public void drain() {
		if(!isEmpty() && isHeat()) {
			//끓인 재료를 다음 단계로 넘김
			empty = true;
		}
	}
	
	public void boil() {
		if(isEmpty() && isHeat()) {
			// 끓임
			heat = true;
		}
	}
	
	private boolean isHeat() {
		return heat;
	}

	public boolean isEmpty() {
		return empty;
	}
}

 

- 이런 가마솥 객체를 만들었다고 가정하자, 가마솥 객체가 두개가 있으면 어떻게될까?

- 대개가 있으면 안되므로 이를 싱글턴으로 바꿔보자.

public class ChocolateGamasot {
	
	private static ChocolateGamasot unique;
	private boolean empty;
	private boolean heat;
	
	private ChocolateGamasot() {
		empty = true;
		heat = false;
	}
	
	public static ChocolateGamasot getInstance() {
		if(unique == null) {
			unique = new ChocolateGamasot();
		}
		return unique;
	}
	
	//가마솟이 비어있을때만 집어넣음.
	public void fill() {
		if(isEmpty()) {
			empty = false;
			heat = false;
			// 원유를 집어넣음
		}
	}
	
	public void drain() {
		if(!isEmpty() && isHeat()) {
			//끓인 재료를 다음 단계로 넘김
			empty = true;
		}
	}
	
	public void boil() {
		if(isEmpty() && isHeat()) {
			// 끓임
			heat = true;
		}
	}
	
	private boolean isHeat() {
		return heat;
	}

	public boolean isEmpty() {
		return empty;
	}
}

 

- 위와같이 싱글턴으로 바꾼뒤 이 소스를 실행시켰더니 문제가생겼다.

//Main 함수
ChocolateGamasot gamasot = ChocolateGamasot.getInstance();

gamasot.fill();
gamasot.boil();
gamasot.drain();

- JVM 내에 두개의 스래드가 위의 코드로 접근했는데 2개의 객체가 생긴것이다!!! (이럴수가!!!)
1번스레드가 if === null 을 확인하고, 그 다음구문을 실행하던중, 다음 스레드가 똑같이 이프문 안으로 분기된것이다.

 

	public static synchronized ChocolateGamasot getInstance() {
		if(unique == null) {
			unique = new ChocolateGamasot();
		}
		return unique;
	}

- 위와같이 동기화 하여 문제를 해결하긴 했는데, 왠지 오버헤드가 너무아까운 생각이든다.. 일단 unique 변수에 ChocolateGamasot를 한번 초기화하기만하면 구지 getInstance() 전체 함수에 동기화를 거는 오버헤드는 필요 없을듯하다.

 

더 효율적으로 getInstance의 동기성을 제어하자

1. getInstance() 의 속도가 중요하지않다면 그냥 둔다.
메소드를 동기화하면 성능이 100배정도 저하된다고한다. 그럼에도 불구하고 getInstance의 속도가 그닥 중요하지않다면 놔두도록하자.

2. 인스턴스를 필요할때 생성하지않고, 바로 생성하여둔다.

	private static ChocolateGamasot unique = new ChocolateGamasot();
	private boolean empty;
	private boolean heat;
	
	public static ChocolateGamasot getInstance() {
		return unique;
	}

- 위 같은방법도 그닥 나빠보이지 않는다 (근데 static 변수만 쓸때와 차이점은 뭐지??)

3."DCL(Dobule Checking Locking)"을 써서 getInstance()에서 동기화 되는 부분을 줄여보자.

private static volatile ChocolateGamasot unique;
private boolean empty;
private boolean heat;

public static ChocolateGamasot getInstance() {
	
	//인스턴스가 있는지 확인하고 synchronized 블럭으로 접근
	if(unique == null) {
		// 이렇게하면 처음에만 동기화 진행.
		synchronized (ChocolateGamasot.class) {
			if(unique == null) {
				unique = new ChocolateGamasot();
			}
		}
	}
	return unique;
}

 

- 잠깐! volatile 이란 무엇일까?
“… the volatile modifier guarantees that any thread that reads a field will see the most recently written value.” - Josh Bloch

이를 해석해보자면, volatile 이란 어떤 쓰레드가 read 하더라도 항상 최근값만 불러온다는 뜻이다.

Volatile을 사용하게 된다면, RAM에서 직접 변수를 읽고 쓰도록 Thread에게 강제한다. 이렇게 되면 스레드는 캐시에 존재하는 복사본이 아닌, RAM에 있는 마지막버전을 확인한다. 스레드가 동기화된 블록으로 들어가게 되면 ( 코드상 sychronizied 부분) 다른 모든 스레드는 첫번째 스레드가 동기화된 블록에서 나갈때 까지 기다린다. (캐시에 접근하지않고 직접 RAM에 직접 읽고 기록)

출처:https://stackoverflow.com/questions/106591/what-is-the-volatile-keyword-useful-for

 

What is the volatile keyword useful for

At work today, I came across the volatile keyword in Java. Not being very familiar with it, I found this explanation: Java theory and practice: Managing volatility Given the detail in which that

stackoverflow.com

c언어의 예시로 쉽게 volatile 설명 :https://dojang.io/mod/page/view.php?id=749

 

- 참고로 DCL은 자바 1.4 버전 이후부터만 쓸 수 있다. (1.4 이전 JVM 중에는 volatille 을 사용하더라도 동기화가 안되는 것이 많았다)

+ Recent posts