[Design Pattern] 자바 디자인 패턴 - Singleton
싱글톤 패턴
개념
- 생성자가 여러번 호출되더라도 실제로 생성되는 객체는 하나이고 최초로 생성 이후 호출되는 생성자는 이미 생성된 객체를 리턴하는 것을 의미한다.
- 주로 공통된 객체를 여러개 생성해서 사용하는 DBCP(DataBase Connection Pool)와 같은 상황에 많이 사용된다.
예제
package basic;
public class HelloSingle {
private static HelloSingle instance = null;
private String msg ="안녕 난 싱글턴이야...";
// 기본 생성자 private으로 만들어 생성을 불가능하게 했다.
// getInstance를 통해서만 생성이 가능하다.
private HelloSingle() { }
// getInstance는 내부적으로 생성되지 않았다면 생성하고
// 기존에 생성된 값이 존재한다면, 기존 인스턴스를 리턴하는 형태를 취한다.
public static HelloSingle getInstance() {
if(instance == null) {
instance = new HelloSingle();
}
return instance;
}
public void prnMsg() {
System.out.println("msg : " + msg);
}
}
- getInstance는 내부적으로 생성되지 않았다면 생성하고, 기존에 생성된 값이 존재한다면 기존 인스턴스를 리턴하는 형태를 취한다.
- 이와 같은 구조로 인해, 프로그램 전반에 걸쳐 하나의 인스턴스만을 유지한다.
- 인스턴스를 제공하는 메서드와 인스턴스 변수 모두 static으로 선언된 정적 변수 및 메서드이다. 기본생성자를 통해 생성할 수 없기 때문에 외부에서 인스턴스에 접근하려면 클래스 변수 및 메서드에 접근을 허용해야 하기 때문에 두 메서드는 static으로 선언된 것이다.
문제점
- 1) 멀티 스레드 환경에서 안전하지 않다는 문제가 발생한다. 여러 스레드가 공유되는 상황에서 아래의 조건문이 동시에 실행될 수 있기 때문에 하나의 인스턴스가 아닌 여러개의 인스턴스가 발생할 위험이 있다.
public static HelloSingle getInstance() {
if(instance == null) {
instance = new HelloSingle();
}
return instance;
}
- 2) 아래와 같이 count는 각기 다른 스레드에서 공유하고 각기 다른 프로세스에서 처리하고 있기 때문에 값이 일관되지 않을 수 있다.
package basic;
public class HelloSingle {
private static HelloSingle instance = null;
private String msg ="안녕 난 싱글턴이야...";
private int count= 0;
// 기본 생성자 private으로 만들어 생성을 불가능하게 했다.
// getInstance를 통해서만 생성이 가능하다.
private HelloSingle() { }
// getInstance는 내부적으로 생성되지 않았다면 생성하고
// 기존에 생성된 값이 존재한다면, 기존 인스턴스를 리턴하는 형태를 취한다.
public static HelloSingle getInstance() {
if(instance == null) {
instance = new HelloSingle();
}
return instance;
}
public void prnMsg() {
count++;
System.out.println("msg(호출 횟수) : " + msg + "(" + count + ")" );
}
}
해결책
- 멀티스레드 환경에서 싱글톤의 문제점을 해결하는 두가지 방안
- 1) static 변수에 인스턴스를 만들어 바로 초기화하는 방법
- 2) 인스턴스를 만드는 메서드에 동기화하는 방법
- 해결책 1) static 변수에 인스턴스를 만들어 바로 초기화하는 방법
- static 변수는 객체가 생성되기 전 클래스가 메모리에 로딩할 때 만들어져 초기화가 한 번만 실행된다. 또한 정적 변수는 프로그램이 시작될 때부터 종료될 때까지 없어지지 않고 메모리에 계속 상주하며 클래스에서 생성된 모든 객체에서 참조할 수 있다. 따라서 조건문에서 instance는 null이 될 수 없으므로 기존 생성된 instance를 리턴하게 되는 것이다.
- 그러나 객체생성 자체는 로드 시점에 결정되어 하나의 객체만을 사용하지만 count에 접근하는 것은 아무 제약없이 동시에 접근이 가능하므로 원치않는 결과를 가져올수도 있다.
- 이를 해결하는 방법은 아래의 코드처럼 synchronized를 통해 여러 스레드에서 동시에 접근하는 것을 막는 방법이다.
package basic;
public class HelloSingle {
private static HelloSingle instance = null;
private String msg ="안녕 난 싱글턴이야...";
private int count= 0;
private HelloSingle() { }
public static HelloSingle getInstance() {
if(instance == null) {
instance = new HelloSingle();
}
return instance;
}
// synchronized를 통해 여러 스레드에서 동시에 접근하는 것을 막는다.
public sinchronized static void prnMsg() {
count++;
System.out.println("msg(호출 횟수) : " + msg + "(" + count + ")" );
}
}
- 이 방법처럼 static을 선언하면 객체를 전혀 생성하지 않고 메서드를 사용할 수 있고 인스턴스 메서드를 사용하는 것보다 성능 면에서 우수하다.
- 해결책 2) 인스턴스를 메서드에 동기화시키는 방법
- 인터페이스를 구현하는 경우, 인터페이스는 static 메서드를 가질 수 없기 때문에 static을 사용할 수 없는 상황이 발생한다.
[ Printer.java 인터페이스 ]
public interface Printer {
public void prnMsg(String msg);
}
[ RealPrinter.java ]
public class RealPrinter implements Printer{
private static Printer printer = null;
private RealPrinter() {
}
public synchronized static Printer getInstance() {
if (printer == null)
printer = new RealPrinter();
return printer;
}
@Override
public void print(String msg) {
System.out.println(msg);
}
}
[ UsePrinter.java ]
public class UsePrinter {
public void doSomething(Printer printer) {
printer.print("fakeGet");
}
}
[ FakePrinter.java ]
public class FakePrinter implements Printer{
private String msg;
public void print(String msg) {
this.msg = msg;
}
public String get() {
return msg;
}
}
[ UsePrinterTest.java ]
public class UsePrinterTest {
public void testdoSomething() {
FakePrinter fake = new FakePrinter();
UsePrinter use = new UsePrinter();
use.doSomething(fake);
assertEquals("fakeGet", fake.get());
}
}
싱글톤의 문제점
- 싱글톤은 프로그램 전체에서 하나의 객체만을 공통으로 사용하고 있기 때문에 각 개체간의 결합도가 높아지고 변경에 유연하게 대처할 수 없다. 싱글톤 객체가 변경되면 이를 참조하고 있는 모든 값들이 변경되어야 한다.
- 멀티스레드 환경에서 대처가 가능하지만 고려해야할 점이 많아 사용이 어렵고, 프로그램 전반에 걸쳐 필요한 부분에만 사용한다면 장점이 존재한다.
댓글남기기