프로그램을 실행하는데 있어서, 하나만 있어도 문제가 없는 객체들도 있다. Singleton Pattern을 사용하면, 전역변수를 사용할 때와 마찬가지로, 어디서든 객체 인스턴스에 접근할 수 있다. 전역변수를 사용할 때와 대비되는 장점이 있는데, 전역변수로 생성된 객체가 자원을 많이 잡아먹는다고 가정할 때, 그 객체가 사실 많이 호출되지 않는다면, 자원만 쓸데없이 낭비한 꼴이 될 것이다. 하지만, Singleton Pattern을 사용하면 필요할 때만 객체를 생성할 수 있다.
public class Singleton{
private static Singleton uniqueInstance; //하나뿐인 인스턴스를 저장하는 정적 변수
private Singleton(){} //생성자가 private로 설정되어있어서 Singleton에서만 객체 생성가능
public static Singleton getInstance(){ //클래스의 인스턴스를 만들어서 리턴
if(uniqueInstance == null){ //uniqueInstance가 null이 아니면, 이미 존재하면 생성X
uniqueInstance = new Singleton(); //uniqueInstance가 존재하지 않기 때문에 생성, 반환
}
return uniqueInstance;
}
}
싱글톤 패턴(Singleton Pattern)은 클래스 인스턴스를 하나만 생성하고, 그 인스턴스로의 전역 접근을 제공한다
초콜릿 공장을 운영하고 있다고 가정해보자. 초콜릿을 제작하는 과정에서 전체적인 공정을 제어하는 인스턴스는 1개인 것이 안정적이기 때문에, 다음과 같이 제작했다.
public class ChocolateBolier{
private static ChocolateBolier uniqueInstance;
private ChocolateBolier(){}
public static ChocolateBolier getInstance(){
if(uniqueInstance == null){
uniqueInstance = new ChocolateBolier();
}
return uniqueInstance;
}
public void fill(){
// 초콜릿을 채움
}
public void drain(){
// 초콜릿을 옮김
}
public void boil(){
// 초콜릿을 중탕함
}
}
아마도 의도한 대로 잘 작동할 것이다. 하지만, 여기서 멀티스레드를 사용한다면 오작동할 가능성이 있다.
만약 2개의 초콜릿 공정 스레드가 있다고 가정하자. 각각의 스레드들은 초콜릿을 만들기 위해서 ChocolateBolier 클래스의 getInstance() 메서드를 호출할 것이고, 이 과정에서 인스턴스가 없다면 생성, 있다면 생성하지 않을 것이다.
먼저 1번 스레드가 getInstance()메서드를 호출한다. 인스턴스가 없기 때문에, 새로운 인스턴스를 생성하려고 하는 찰나, 타임아웃으로 인해서 2번 스레드가 작동된다면, 아직 인스턴스가 생성되지 않았기 때문에, 인스턴스를 생성할 것이다. 그렇게 2번 스레드가 완료되고 다시 1번 스레드가 작동한다면, 이전에 멈춰있던 부분부터 다시 실행하여 인스턴스를 마저 만들게 되고 그렇게 2개의 인스턴스가 생성되게 된다.
이를 해결하기 위해서 getInstance() 에 synchronized 키워드를 추가하여, 하나의 스레드가 메서드의 사용을 끝내기 전까지 다른 스레드들을 대기해야 한다.
public static synchronized ChocolateBolier getInstance(){
if(uniqueInstance == null){
uniqueInstance = new ChocolateBolier();
}
return uniqueInstance;
}