싱글톤 패턴

이연희·2022년 7월 17일
0

Design_Pattern

목록 보기
3/11

싱글톤

하나의 인스턴스

Printer 클래스가 한번만 호출되도록 하는 방법은 단순하게 생각하여 생성자를 private으로 선언하면 된다. 이렇게 변경하면 외부에서 new Printer()를 더 사용할 수 없게 된다.

public class Printer{
	//public Printer(){}
    private Printer(){}
    public void print(Resource r){...}
}

그러나 프린터 인스턴스는 하나 만들어야 되기 때문에, 이 인스턴스를 외부에 제공해줄 메서드가 필요하다.
아래 코드에서 중요한 점은 getPrinter메서드와 printer 변수가 static타입으로 선언되었다는 점이다. 정적메서드, 정적변수는 이들이 구체적인 인스턴스에 속하는 영역이 아니고 클래스 자체에 속한다는 의미이다. 따라서 클래스의 인스턴스를 통하지 않고도 메서드를 실행할 수 있고 변수를 참조할 수 있다.

public class Printer{
	private static Printer printer = null;
    private Printer(){}
    
    public static Printer getPrinter(){
    	if(printer==null){
        	printer = new Printer();
        }
        return printer();
    }
}

문제점

다중 쓰레드에서 Printer 클래스를 이용할 때 인스턴스가 1개 이상 생성될 수 있다. Printer의 getPrinter() 메서드를 실행하고 if문에 들어가고 객체를 생성하기 전에, 다른 쓰레드가 getPrinter()를 호출하고 if 문에 들어가 printer 객체를 만들면 총 2개의 인스턴스가 생성된다. 이런 현상을 경쟁상태(race condition)가 발생했다고 한다. 메모리와 같은 동일한 자원을 2개 이상의 쓰레드가 이용하려는 현상을 말한다.

아래 코드에선 인스턴스가 1개 이상 생기면 문제가 발생한다. Printer 클래스가 상태를 유지해야 하는 경우에 문제가 발생한다. Printer 클래스와 같이 counter 변수와 같은 값을 인스턴스가 유지해야 한다.

public class Printer{
	private static Printer printer = null;
    private int counter=0;
    private Printer(){}
    
    public static Printer getPrinter(){
    	if (printer==nul){
        	try{
            	Thread.sleep(1);
            }
            catch(InterruptedException e){}
            printer=new Printer();//인스턴스 생성
        }
        return printer;
    }
    public void print(String str){
    	counter++;
      	System.out.println(str+counter);
    }
}
1-thread print using Printer@1xfs. 1
5-thread print using Printer@98vc. 1
3-thread print using Printer@s9vd. 1
2-thread print using Printer@s9vd. 2
3-thread print using Printer@s9vd. 3

이와 같이 Printer 클래스의 인스턴스가 상태를 유지해야 한다면 문제가 발생한다. 이는 인스턴스마다 counter 변수를 각각 만들어 유지하기 때문이다.

해결책

다중 쓰레드 애플리케이션에서 발생하는 문제를 해결하는 방법 2가지는 다음과 같다.
1. 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법
2. 인스턴스를 만드는 메서드에 동기화하는 방법

1) 정적 변수에 Printer 인스턴스를 만들어 바로 초기화

정적변수는 객체가 생성되기 전 클래스가 메모리에 로딩될 때 만들어져 초기화가 한 번만 실행된다. 또한 프로그램이 끝날때까지 메모리에 상주하며 클래스에서 생성된 모든 객체에서 참조할 수 있다. 이 방법은 다중 쓰레드 환경에서 문제를 일으켰던 if(printer==null)을 원척적으로 제거하기 위한 방법이다.

public class Printer{
	private static Printer printer = new Printer();
    private int countre = 0;
    private Printer(){}
    
    public static Printer getPrinter(){
    	return printer;
    }
}

2) 인스턴스를 만드는 메서드에 동기화

Printer 클래스의 객체를 얻는 getPrinter 메서드를 동기화하는 코드이다. 다중 쓰레드 환경에서 동시에 여러 쓰레드가 getPrinter 메서드를 소유하는 객체에 접근하는 것을 방지한다. 결과적으로 Printer 클래스의 인스턴스가 오직 하나의 인스턴스만 생성한다.
그리고 여러개의 쓰레드가 하나뿐인 counter 변수 값에 동시에 접근해 갱신하지 않도록 하기 위해서 counter 변수를 변경하는 부분도 동기화를 해준다.

public class Printer{
	private static Printer printer = null;
    private Printer(){}
    
    public synchronized static Printer getPrinter(){
    	if(printer==null){
        	printer=new Printer();
        }
        return printer();
    }
    public void print(String str){
    	synchronized(this){//오직 하나의 쓰레드만 접근을 허용
        	counter++;
            System.out.println(str+counter);
        }	
    }
}	

싱글톤 패턴

싱글톤 패턴은 인스턴스가 오직 하나만 생성되는 것을 보장하고 어디에서든 이 인스턴스에 접근할 수 있도록 하는 디자인 패턴이다.
클라이언트가 싱글톤 클래스에 getInstance 메서드를 통해 객체 생성을 요청하면 이미 객체가 있는 경우는 객체를 반환하고, 아닌 경우는 생성자를 호출해 객체를 생성한다.

싱글톤 패턴과 정적 클래스

정적 메서드로만 이루어진 정적 클래스를 사용해도 동일한 효과를 얻을 수 있다. Printer 클래스를 싱글톤 패턴으로 구현하지 않고 정적 클래스로 구현한 코드이다. 정적 클래스를 이용하면 싱글톤 패턴과 차이점은 객체를 전혀 생성하지 않고 메서드를 사용하는 것이다.

public class Printer{
	private static int counter=0;
    public synchronized static void print(String str){//메서드 동기화
    	counter++;
        System.out.println(str+counter);
    }
}

public class UserThread extends Thread{
	public UserThred(String name){
    	super.name;
    }
	public void run(){
    	Printer.print(Thread.cuurentThread().getName()+" print using "+".");
    }
}

하지만 인터페이스를 구현해야 하는 경우에는 정적 메서드를 사용할 수 없다. 아래 코드는 허용되지 않는다.

public interface Printer{
	public static void print(String str);//허용되지 않음
}

인터페이스를 사용하는 주된 이유는 대체 구현이 필요한 경우다. 특히 단위 테스트를 수행할 때 매우 중요하다.

profile
공부기록

0개의 댓글