- 소프트웨어를 설계할 때 특정 맥락에서 자주 발생하는 고질적인 문제들이
발생했을 때 재사용할수 있는 해결책
- 프로그램 내의 여러 곳에서 반복적으로 사용되어야 하는 객체에 대해 전역적인
범위를 갖도록 객체를 생성하고, 이 객체를 여러 곳에서 공유하기 위한 클래스
작성 패턴- 이러한 패턴을 통해 생성된 객체를 싱글톤 객체라고한다.
- 프로그램의 전역에서 활용할 재료로 사용되는 공유 기능을 하나만 생성하여
여러곳에서 재사용함으로 해서 메모리를 효율적으로 사용할 수 있다.- 단 한번만 객체를 생성하면 다시 객체를 생성할 필요가 없기 때문에,
해당 기능을 사용할 때마다 객체를 일일이 생성해야 하는 번거로움을 피할 수 있다.
public class Singleton {
// 단 1개만 존재해야 하는 객체의 인스턴스, static 으로 선언
private static Singleton instance;
// private 생성자로 외부에서 객체 생성을 막음
private Singleton() {}
// 외부에서는 getInstance() 로 instance 를 반환
public static Singleton getInstance() {
// instance 가 null 일 때만 생성
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
- 싱글톤 패턴의 기본적인 구현 방법 입니다.
- private 생성자 로 외부에서 객체 생성을 막으며, getInstance() 함수 로만 instance 에 접근이 가능하게 만듭니다.
- instance 는 static 변수 로 선언되며 getInstance() 가 최초로 호출 될 때 에는 null 이므로 한 번만 생성하고, 이후에는 생성해놨던 instance 를 반환합니다.
- 따라서 다른 함수에서 getInstance() 를 여러 번 호출 하더라도 단 하나의 동일한 instance 만을 반환 해 줍니다.
- 마치 앞에서 살펴봤던 예시 처럼 여러명이 하나의 프린터를 사용하는 상황과 동일한 구현인 것 이죠!
- 하지만, 위의 코드는 멀티 스레드 환경에서 Thread-Safe 를 보장해주지 않습니다.
- 두 개의 스레드에서 동시에 getInstance() 를 호출 한경우 에
if (instance == null) 조건문에 동시에 도달하게 되어 instance 를 두번 생성할 수 도 있기 떄문이죠!- 이를 해결하기 위해, Java 에서 스레드 동기화를 지원하는 Synchronzied 를 사용할 수 있습니다.
private static Singleton instance;
private Singleton() {}
// synchronzied 스레드 동기화를 사용하며 멀티 스레드 환경에서의 동시성 문제 해결
public static synchronzied Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 위처럼, 여러개의 스레드가 동시에 호출 할 수 있는 getInstance() 함수에 Synchronzied 를 적용하여 멀티 스레드 환경에서 getInstance() 함수 호출 시에 발생할 수 있는 동시성 문제를 해결 할 수 있습니다.
- Synchronzied 의 가장 큰 단점은, Thread-Safe 를 보장하기 위해 속도를 포기했다는 점 입니다.
- Synchronzied 를 적용하면 보통 적용하지 않았을 때 보다 약 100배 정도 속도가 느려지는 단점이 있다고 알려져 있습니다. 물론 정확한 수치는 직접 측정해 봐야 알겠지만, 어쩄든 성능저하가 있는 것은 확실하죠.
public class Singleton {
private volatile static Singleton instance;
private Sigleton() {}
// Double check
public Singleton getInstance() {
// instance 가 null 인 경우
if(instance == null) {
// synchronized 를 적용한 후, 한번 더 instance 가 null 인지를 체크
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- Double Check 의 아이디어는, getInstace() 함수 전체에 Synchronzied 를 적용하는 것을 개선하기 위해 Synchronzied 를 사용하지 않고 instance 가 null 인지를 확인한 후, null 이라면 이때서야 Synchronzied 를 적용하여 스레드 동기화를 한 후 instance 가 null 인지 다시한번 체크하는 방법 입니다.
- 처음부터 Synchronzied 를 적용하기 보다 느리게, 게으르게 Synchronzied 를 적용하여 한번 더 체크하는 것 이죠!
- 이러한 아이디어는 Lazy~ 라는 키워드로 컴퓨터 공학의 다양한 분야에 적용되는 성능 개선 방식입니다.
- 굳이 처음부터 정직하게 스레드를 동기화 하여 확인할 필요는 없는 것 이죠! instance 가 null 이라면 그때 가서Synchronzied 를 적용한 후에 게으르게 한번 더 확인하면 되니까요.
- 이처럼 Lazy 한 Double Check 방식으로 Synchronzied 의 속도 저하를 어느정도 개선할 수 있습니다.
public class Singleton {
private Singleton() {}
// private static inner class 인 LazyHolder
private static class LazyHolder {
// LazyHolder 클래스 초기화 과정에서 JVM 이 Thread-Safe 하게 instance 를 생성
private static final Singleton instance = new Singleton();
}
// LazyHolder 의 instance 에 접근하여 반환
public static Singleton getInstance() {
return LazyHolder.instance;
}
}
- Java 진영에서 가장 많이 사용되는 LazyHolder 를 사용하는 싱글톤 패턴 입니다.
- private static class 인 LazyHolder 안에 instace 를 final 로 선언하는 방법인데요,
- JVM (Java Virtual Machine) 의 클래스의 초기화 과정에서 원자성을 보장하는 원리를 이용하는 방식입니다.
- getInstance() 가 호출되면 LazyHolder의 instance 변수에 접근하는데요, 이때 LazyHolder 가 static class 이기 때문에 클래스의 초기화 과정이 이루어 집니다.
- LazyHolder 클래스가 초기화 되면서 instance 객체의 생성도 이루어 지는데요, JVM 은 이러한 클래스 초기화 과정에서 원자성을 보장합니다.
- 따라서, final 로 선언한 instance 는 getInstace() 호출 시 LazyHolder 클래스의 초기화가 이루어 지면서 원자성이 보장된 상태로 단 한번 생성되고, final 변수 이므로 이후로 다시 instance 가 할당되는 것 또한 막을 수 있습니다.
- 이러한 방법에 장점은 Synchronzied 를 사용하지 않아도 JVM 자체가 보장하는 원자성을 사용하여 Thread-Safe 하게 싱글톤 패턴을 구현할 수 있는 것 입니다.
- M : Model
-> 사용자가 원하는 데이터나 정보를 제공
-> 애플리케이션의 정보, 데이터를 나타낸다.
-> 이러한 data, 정보들의 가공을 책임지는 영역을 말한다.
- V : View
-> 보여지는 화면
-> input 텍스트, 체크박스 항목 등과 같은 사용자의 인터페이스 요소를
나타냅니다. 데이터 및 객체의 입력, 그리고 보여주는 출력을 담당한다.
-> 데이터를 기반으로 사용자들이 볼 수 있는 화면이다.
- C : Controller
-> 사용자의 요청을 처리하고, 그 요청에 따른 전체적인 흐름 제어
-> 데이터와 사용자 인터페이스 요소들을 잇는 다리역할을 한다.
- MVC1 패턴의 경우 View와 Controller를 모두 JSP가 담당하는 형태를 가진다.
즉, JSP하나로 유저의 요청을 받고 응답을 처리하므로 구현의 난이도는 쉽다.
- 단순한 프로젝트에는 괜찮겠지만, 내용이 복잡하고 거대해질수록 이 패턴은
힘을 잃는다. 즉 유지보수에 있어서 문제가 발생한다.
- MVC2패턴은 널리 표준으로 사용되는 패턴입니다.
- 요청을 하나의 컨트롤러(Servlet)가 먼저 받는다. 즉 MVC1과는 다르게
Controller, View가 분리되어 있다.
- 따라서 역할이 분리되어 MVC1 패턴에서의 단점을 보완할 수 있다.
- 그러므로 개발자는 M,V,C 중에서 수정해야 할 부분이 있다면, 그것만
꺼내어 수정하면 된다.