본문 바로가기

Developer/Java Design Pattern

chapter 06. Singleton

06. Singleton Pattern과 Object Pool Pattern


웹 어플리케이션을 제작할 때는 꼭 사용해야 하는 패턴중 하나이다. 기본적으로 'static'이라는 자바 키워드가 가진 의미를 정확히 파악하고 있어야 한다.




6.1 static이란?

  • 자바에서는 전역변수라는 것이 존재하지 않는다.

  • 자바에서는 외부의 임의의 클래스에서 광역적으로 접근할 수 있도록 'static'이라는 키워드를 제공한다.


6.2 Singleton 패턴

  • 자바는 객체로 시작해서 객체로 끝나는 언어로 객체생성에 많은 자원을 소비한다. 

    ① 객체 생성을 할 수 있는 가용 공간이 있는지 확인

    ② 강요 공간이 있다면 힙(Heap)메모리 영역에 적재하고 참조값을 생성

    ③ 이 객체가 언제 메모리 영역에서 제거돼야 하는지 자바 가상머신이 실행 프로그램이 종료될 때 까지감시하고 메모리에서 제거

  • 더는 참조하지 않는 생성된 객체들이 메모리 공간 점유율이 높아지면 이들을 제거한다.

  • 가비지 컬렉팅이 운영체제 레벨에서 스레드 단위로 발생한다.

  • 메모리 확보의 필요성이 인지되면 가비지 컬렉팅 스레드가 작동하게 되는데 수행되는 동안 CPU 사용률이 높아진다.


클래스를 통해서 생성할 수 있는 객체가 단 하나면 충분할 수가 있다는 것을 그 이상의 객체 생성을 차단하는 것이 Singleton 패턴이다.


6.3 Singleton 패턴은 어떻게 만드는가?

- 앞에서 설명했던 static의 특징을 이용해야 한다.

public class HelloSingleton {

	// 자신의 객체를 정적 변수로 선언
	private static HelloSingleton helloSingleton = new HelloSingleton();

	// 기본생성자를 이용하여 객체를 생성
	public static HelloSingleton getSingleton() {
		return helloSingleton;
	}
}

- 자신의 객체를 정적변수로 선언하고 기본생성자를 이용하여 객체를 생성

- 변수의 은닉성을 적용하여 메소드를 통해서만 접근 가능토록 하는데, 이 메소드 또한 정적 메소드로 선언되어 있음.

- 모두 정적(static)으로 선언되어 있음.

public class HelloSingletonTest {

	public static void main(String[] args) {

		HelloSingleton s1 = HelloSingleton.getSingleton();
		HelloSingleton s2 = HelloSingleton.getSingleton();
		
		System.out.println("s1 == s1 ?" + (s1==s2));
	}
}

[결과]


- 실행해보면 Singleton 클래스를 통해서 반환받는 개체 s1, s2은 같은 곳을 참조하고 있다. 즉 객체가 하나만 생성되어 있다는 증거.

- 하지만 위의 코드는 완벽한 Singleton패턴이 아니다.

public class HelloSingletonTest {

	public static void main(String[] args) {

		HelloSingleton s = HelloSingleton.getSingleton();
		
		HelloSingleton s1 = HelloSingleton.getSingleton();
		HelloSingleton s2 = new HelloSingleton();
		
		System.out.println("s == s1 ?" + (s==s1));
		System.out.println("s == s2 ?" + (s==s2));
	}
}

[결과] 


- 위 예제를 실행하면 새로운 객체 s2가 생성된 것을 볼 수 있다.

- 싱글톤은 오로지 하나의 객체만 생성될 수 있도록 하고자 하는 것이다.

- 위의 결과가 발생할 수 있는 원인은 HelloSingleton 클래스가 가진 생성자가 public으로 접근할 수 있어 객체 생성이 가능하다.

- HelloSingleton 생성자의 접근을 막아야 한다.

- 생성자 메소드 접근 지정자를 외부로부터 감추어 은닉성을 적용시켜야한다.

public class HelloSingleton {

	// 자신의 객체를 정적 변수로 선언
	private static HelloSingleton helloSingleton = new HelloSingleton();
	
	// 은닉성을 적용하여 외부의 클래스로부터의 접근을 막는다.
	private HelloSingleton() {}
	
	// 기본생성자를 이용하여 객체를 생성
	public static HelloSingleton getSingleton() {
		
		return helloSingleton;
	}
}


◈정리: Singleton 클래스 작성법

// Code start

// 1.
private HelloSingleton() {}
// 2.
HelloSingleton single = new HelloSingleton();
// 3.
static HelloSingleton single = new HelloSingletion();
// 4.
private static HelloSingleton single = new HelloSingleton();
// 5.
public static HelloSingleton getSingleton() {
	
	3return helloSingleton;
}

// Code end
  1.  객체 생성을 위해서는 생성자가 필요하지만, 임의의 클래스에서 생성자로 접근할 수 있다면 객체를 마구잡이로 생성할 수 있으므로 생성자에 대한 접근 제약이 필요하다.
  2.  임의의 클래스에서 Singleton 클래스의 객체 생성을 위한 생성자로의 접근이 차단됐으므로 Singleton 클래스의 객체를 생성할 수 있는 클래스는 Singleton클래스 밖에 없다.
  3.  객체 생성이 클래스 호출을 통해서만 가능하도록 static을 선언한다.
  4.  접근 제약이 필요하다.
  5.  외부에서 접근할 수 있는 통로를 메소드로 제공한다.



6.4 Object Pool 패턴은 언제 사용하는가?

  • Object Pool 패턴은 Singleton패턴과 상반되는 개념일 수 있다.
    - Singleton : 객체를 하나만 생성
    - Object Pool : 객체를 여러개 생성
  • 사용 사례 : 웹 프로그램을 작성할 때 데이터베이스에 접근하기 위해 Connection 객체를 참조하는 방식이 Object Pool 패턴의 대표적인 예
  • 클라이언트들이 웹 서버에 요청(Request)을 보내면 웹 서버의 Servlet / JSP는 JDBC를 이용해 데이터베이스 서버와 작업을 수행하고 클라이언트에게 응답(Response) 한다.
  • 요청이 발생할 때마다 Connection 객체를 생성하고 결과를 반환해주고 처리가 끝난 Connection 객체를 가비지 콜렉팅하기에는 객체 생성에 많은 자원소모와 시간낭비이다. 
  • Connection 객체들(Object Pool)을 생성해놓고, 거기에 Connection 객체를 채워뒀다가 클라이언트 요청으로 DB에 접근해야 할 때 객체를 할당받아 사용하고 처리완료가 되면 수거한다.
  • 예를들면 스키장에서 보드복을 100개 만들어놓고 보드탈라고 보드복을 빌려야되는 사람들에게 빌려주고 다 쓰면 반납한다.