인스턴스를 하나만 유지하고 싶을 때 사용하는 패턴으로, 전역변수를 사용하지 않고 객체를 전역적으로 액세스할 수 있는 방법을 제공할 수 있다.
1. 다음과 같은 로그를 남기는 Logger클래스가 있다 하자.
public class Logger {
public void log(String message) {
System.out.println(message);
}
}
2. 시스템 내의 여러군데에서 Logger클래스를 사용하려면 다음과 같이 사용해야 한다.
public class Client {
public static void main(String[] args) {
Logger logger1 = new Logger();
Logger logger2 = new Logger();
logger1.log("This is from logger1");
logger2.log("This is from logger2");
}
}
위 코드의 Logger 클래스는 파일에 저장하지 않고 print하는 코드이다. 만약 로그 메시지를 파일에 저장한다고 가정해보면, 각 Logger 인스턴스마다 파일에 쓰기 작업을 수행하게 되기 때문에 동일한 파일에 동시에 여러 번 쓰기를 시도하게 될 수 있다. 이런 경우에는 데이터가 꼬이거나 누락될 수 있다.
Logger를 사용할 때 같은 객체를 사용하도록 하자!
instance
는 외부 접근을 막기 위해 private으로 한다.getInstance
로만 한다.instance
와 getInstance
는 static으로 선언한다.instance에 접근할 때 다른 코드는 Singleton.getInstance()로 instance를 받을 수 있다.
1. Singeton
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
getInstance함수는 instance가 null일 때를 구분해 최초 실행 시 instance를 생성하도록 구현한다.
getInstance함수를 보면 instance를 생성하기 전에 null인지 확인한다.
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
하나의 인스턴스를 제공하기 위해 기존에 생성되어있는지 확인하는 것은 당연하다!
인스턴스를 생성하기 전에 null인지 체크하고 객체를 생성하는 방법을 lazy initialization
이라 한다. Singleton객체를 사용하지 않을 때는 생성 즉 메모리를 할당하지 않는다는 것이다.
lazy initialization
을 사용하지 않으면instance
를 정의할 때 바로 생성해주어야 한다.public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
멀티 쓰레드 환경에서 lazy initialization
을 사용하면 복수의 객체가 생성될 수 있다. 바로 instance가 null일 때 동시에 2개 이상의 쓰레드로부터 getInstance가 호출될 때이다. 예를 들면, 쓰레드A와 B가 있다 할 때 쓰레드A가 instance=new Singleton()
에 접근하기 직전에 쓰레드B가 if(instance==null)
에 접근하는 것이다. 결국 new Singleton()
이 두번 실행되는 상황이 된다.
위 문제 상황을 해결하기 위해 임계 구역(Critical Section)
을 형성한다. JAVA에서는 synchronized
를 사용하지만 C++에는 뮤텍스
를 사용하는 방법이 있다.