싱글톤 패턴은, 디자인 패턴중의 한 종류이다.
흔히들 프로젝트를 해봤으면,
MVC패턴 정도는 알고 있을거다.
MVC패턴도 디자인 패턴중의 하나이고,
디자인 패턴이란 객체 지향 프로그래밍 설계를 할 때
자주 발생하는 문제들을 피하기 위해 사용되는 패턴이다.
각설하고,
객체를 생성하는데 자원이 많이 든다.
그리고
여러개의 객체가 아닌 데이터 공유를 단 하나의 객체에서 해야한다.
(커넥션풀, 스레드풀 등과 같은 데이터. )
라는 문제가 생겼을때 어떻게 해야할까...?
이때 사용하는 디자인패턴중 하나가 싱글톤 패턴이다.
아래의 그림으로 예시를 들겠다.
사진출처 : https://refactoring.guru/ko/design-patterns/singleton
문을 여는 친구가 참조하는 클래스이고, 문을 의존성 주입, 안에 있는 친구를 싱글톤 객체라고 하자.
그럼 수많은 클래스에서 참조하더래도(문여는 수많은 친구들이 있더라도),
수많은 문이 열리더라도(의존성 주입을 여러번 수행하더라도),
안에 있는 친구(싱글톤 객체)는 단 하나이다.
약간 느낌이 오는가..?
그럼 설명을 시작하겠당.
싱글톤 패턴이란,
하나의 인스턴스만 생성하여 재사용하는 디자인 패턴이다.
코드 예시는 아래와 같다.
실제로 생성되는 객체는 하나(아래의 number)이고,
최초로 생성된 이후에 호출된 생성자
(생성자 역할은 private 생성자가 아닌 getInstance() 매서드)는 이미 생성한 객체(number)를 반환한다.
public class Number {
// 싱글톤이기 때문에, 넘버 라는 클래스의 객체가 단 한개만 존재해야 하므로
// private static으로 선언.
// 현재는 number에 어떤 값을 할당해주지 않았다. 즉, null값
private static Number number;
// 생성자도 private로 선언하여 외부에서 객체 생성도 막아주어야 함.
private Number() {
}
// 그렇다면, 해당 number 객체를 사용하기 위해서는 아래의 getInstance 매서드를 사용해야 한다.
// number 객체가 null 이라면 생성해서 주고, 아니라면 기존의 number 객체를 return 한다.
// 해당 매서드와 싱글톤 패턴의 단점 2번과 연결된다.
public static Number getInstance() {
if(number == null) {
number = new Number();
}
return number;
}
}
public class Number {
// getInstance의 synchronized 빼고는 위와 전부 똑같다.
private static Number number;
private Number() {
}
public synchronized static Number getInstance() {
if(number == null) {
number = new Number();
}
return number;
}
}
synchronized 로 인해 여러 스레드가 동시에 접근하지 못하고 순차적으로 실행되기 때문에 동기화가 보장된다.
즉, 한번 생성이 되었다면,
getInstance() 안의
if(number ==null) 로직은 작동하지 않는다는 말.
하지만, synchronized는 큰 성능저하를 발생시키므로 권장하지 않는다.
public class Number {
private static Number number;
private Number() {
}
//해당 부분 이중 if문으로 변경
public static Number getInstance() {
if(number == null) {
synchronized (Number.class){
if(number == null){
number = new Number();
}
}
}
return number;
}
}
조건문으로 인스턴스의 존재 여부를 확인한 다음 두번째 조건문에서 synchronized를 통해 동기화를 시켜 인스턴스를 생성하는 방법
하지만 이 방법 또한 추천되지 않음. (자바 5 이전에 깨짐.)
public class Number {
private Number() {
}
//여기부터
//LazyHolder 클래스 추가
private static class LazyHolder {
public static final Number number = new Number();
}
public static Number getInstance() {
return LazyHolder.number;
}
//여기까지 추가
}
getInstance() 매서드가 한번만 실행되어도,
LazyHolder.number이 호출되어 LazyHolder Class가 로딩되며
public static final Number number = new Number(); 가 실행되어
초기화가 진행된다.
JVM은 클래스 로딩을 수행할 때 이미 로드된 클래스는 다시 로드하지 않고,
이미 로드된 클래스의 정보를 사용한다.
즉, getInstance() 매서드가 실행되며
LazyHolder.number이 호출되고
LazyHolder Class가 로딩되는데, 단 한번만 로딩되므로
해당 클래스 안의
public static final Number number = new Number();
로직이 한번만 수행된다.
그렇게 여러개의 객체가 생성되는것을 막고, 싱글톤이 수행된다.
실제로 가장 많이 사용하는 일반적인 싱글톤 클래스 사용 방법이다.