해당 포스트는 "자바 객체지향 디자인 패턴", "JAVA 언어로 배우는 디자인 패턴 입문" 책의 내용을 요약한 것이다.



- 자바의 객체 생성

1. 객체 생성을 할 수 있는 가용 공간이 있는지 확인한다.

2. 가용 공간이 있다면 힙(Heap) 메모리 영역에 적재하고, 참조값을 생성해야 한다. 이러한 작업은 런타임(Runtime)시에 시행된다.

3. 생성된 객체가 언제 메모리 영역에서 제거돼야 하는지 JVM이 실행 프로그램이 종료될 때까지 감시하고 메모리에서 제거해야 한다.

 

=> 참조 되지 않는 생성된 객체들이 메모리 공간 점유율이 높아져 메모리 확보의 필요성이 인지되면 가비지 컬렉팅 스레드가 우리의 의지와 상관없이 생성된 객체들을 제거하는 작업을 하게 된다. 이런 작업을 수행하는 동안 CPU 사용률이 높아져 사용자의 응답 시간이 길어지거나 서버가 멈출 수도 있다. 즉, 객체의 생성, 소멸, 관리가 애플리케이션과 시스템에 중대한 영향을 미치므로 필요 이상의 객체가 생성되지 않도록 주의해야 한다.

 

 

※싱글턴(Singleton) 패턴

: 객체가 단 하나면 충분할 수 있다는 것을 깨닫고 오직 하나만 생성되는 것을 보장하고 어디에서든 이 인스턴스에 접근할 수 있도록 하는 디자인 패턴 

 

ex) 회사에 복사기가 하나 있다고 가정하고 복사기를 관리하는 프로그램을 만들어보자

 

public class Copier{
    public Copier(){}
    public void copy(Resource r){
         ....
    }
}

그러나 복사기는 하나이기 때문에 위와 같이 코딩할 경우 new Copies()가 여러 번 호출될 수 있다. 따라서 인스턴스를 하나만 만들어 지도록 강제해야 한다. 그래서 아래와 같이 싱글턴 패턴을 적용했다.

public class Copier
   private static Copier copier = null;
   private Copier(){}
   
   public static Copier getCopier(){
        if(copier == null)
              copier = new Copier();
        return copier;
   }
   public void copy(Resource r){
            ....
   }
}

위 코드를 보다시피 생성자를 private로 해서 new Copier(); 가 호출되지 않도록 강제해 여러 개의 객체가 생성 되는 것을 막았다. getCopier() 메서드는 복사기 인스턴스가 이미 생성되어 있는지를 검사하고, 처음 호출 되어서 인스턴스가 생성되지 않았다면 내부에서 생성자를 호출에 인스턴스를 생성했다. 또한 메서드를 static으로 선언해 인스턴스를 통하지 않고 메서드를 실행할 수 있다. 따라서 Copier.getCopier() 메서드를 통해 static으로 선언된 copier 단 하나의 인스턴스를 참조할 수 있게 된다.

 

 

- 문제점 : 위 싱글턴 패턴 코드는 평소에는 문제가 없지만 다중 스레드 상황에서 문제가 생길 가능성이 크다.

ex) Copies 인스턴스가 생성되지 않았을 때 스레드 1이 getCopier 메서드의 if문을 실행한다. 그러다 스레드 1이 생성자를 통해 인스턴스를 만들기 전에 스레드 2가 if문을 실행해 Copier 인스턴스가 null 인지 확인한다. 아직 스레드 1에서 인스턴스를 만들지 않았기 때문에 if문을 true 값으로 통과하게 되고 스레드 2에서도 생성자를 통해 인스턴스를 만드는 과정에 들어가게 된다. 따라서 Copier 클래스의 인스턴스가 2개가 되는 문제점이 생긴다. 또한 copy() 메서드가 아래와 같다면 스레드 1의 counter와 스레드 2의 counter가 따로 존재하게 되는 문제가 생긴다. 

public void copy(Resourse r){
   counter++;
}

 

*  위 시나리오를 경합 조건 현상이라고 한다. 경합 조건이란 메모리와 같은 동일한 자원을 2개 이상의 스레드가 이용하려고 경합하는 현상을 말한다.

 

 

- 해결책

1. 정적 변수에 인스턴스를 만들어 바로 초기화 하는 방법

public class Copier{
   private static Copier copier = new Copier();
   private int counter = 0;
   private Copier(){}
   
   public static Copier getCopier(){
      return copier;
   }

   public void copy(Resource r){
     counter++;
   }

위 코드는 copier static 변수를 선언과 동시에 정의하여 문제를 일으켰던 if(copier==null) 코드를 지웠다. 위와 같이 하면 프로그램 실행부터 종료 때까지 무조건 하나의 인스턴스가 유지된다. 하지만 여러 개의 스레드가 counter 변수에 동시에 접근해 갱신하기 때문에 copy() 함수에 문제가 있어 synchronized를 붙여 주는 게 좋다.

 

 2. 인스턴스를 만드는 메서드를 동기화 하는 방법

public class Copier
   private static Copier copier = null;
   private Copier(){}
   
   public synchronized static Copier getCopier(){
        if(copier == null)
              copier = new Copier();
        return copier;
   }
   public void copy(Resource r){
            synchronized(this){
                counter++;
           }
   }
}

위와 같이 getCopier() 함수와 copy() 함수에 synchronized를 붙여준다면 한 스레드만 접근 가능하기 때문에 문제점이 해결된다.

 

 

정적 클래스

싱글턴 패턴을 사용하지 않고 정적 메서드로만 이루어진 정적클래스를 사용해도 동일한 효과를 얻을 수 있다.

public class Copier{
   private static int counter = 0;
   public synchronized static void print(Resource r){
        counter++;
   }
}
위와 같이 코딩하면 Copier.print() 로 접근할 수 있다. 하지만, Copier 클래스를 인터페이스로 만들 시 인터페이스에서는 static 변수가 혀용되지 않기 때문에 정적 클래스의 사용이 불가능하다.


 싱글턴 패턴 유의점

: 싱글턴 클래스에 직렬화 기능을 추가하려면 implements Serializable을 추가하는 것으론 부족하다. 싱글턴 특성을 유지 하려면 모든 필드를 transient로 선언하고 readResolve 메서드를 추가해야 한다. 그렇지 않으면 역직렬화될 때마다 새로운 객체가 생기게 된다.


 enum 싱글턴

: enum 싱글턴은 더 간결하게 코드 작성이 가능하고 직렬화가 자동으로 처리된다. 멀티쓰레드로부터 안전하다. 또한 일반적인 싱글턴 패턴은 리플렉션을 통한 공격 위험이 있다. AccessibleObject, setAccessible 메서드로 권한을 획득한 클라이언트가 리플렉션 기능을 통해 private 생성자를 호출할 수 있다. 이런 공격에도 enum 싱글턴은 안전하다. 아직 널리 사용되지는 않는 사용법이지만, 싱글턴을 구현하는 가장 좋은 방법이다. 

public class test {

	public static void main(String args[]) throws Exception {
		EnumSingleton.INSTANCE.print();
	}

}

enum EnumSingleton{
	INSTANCE;
	String test;
	
	public void print(){
		test = "test";
		System.out.println(test);
	}
}


'자바 > 디자인패턴' 카테고리의 다른 글

커맨드(Command) 패턴  (0) 2017.07.03
상태(State) 패턴  (0) 2017.07.03
스트래티지(Strategy, 전략) 패턴  (0) 2017.07.01
디자인 패턴  (0) 2017.06.29
책임 연쇄/사슬 패턴  (0) 2017.06.08

+ Recent posts