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
- 객체 생성을 위해서는 생성자가 필요하지만, 임의의 클래스에서 생성자로 접근할 수 있다면 객체를 마구잡이로 생성할 수 있으므로 생성자에 대한 접근 제약이 필요하다.
- 임의의 클래스에서 Singleton 클래스의 객체 생성을 위한 생성자로의 접근이 차단됐으므로 Singleton 클래스의 객체를 생성할 수 있는 클래스는 Singleton클래스 밖에 없다.
- 객체 생성이 클래스 호출을 통해서만 가능하도록 static을 선언한다.
- 접근 제약이 필요하다.
- 외부에서 접근할 수 있는 통로를 메소드로 제공한다.
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개 만들어놓고 보드탈라고 보드복을 빌려야되는 사람들에게 빌려주고 다 쓰면 반납한다.