자바 싱글톤(Singleton) 패턴
싱글톤 패턴이란?
- 인스턴스를 오직 한개만 제공하는 클래스
조건
- 인스턴스를 오직 하나만 만들어야함
- 인스턴스에 글로벌하게 접근할 수 있는 방법을 제공해야함
싱글톤 패턴을 사용하는 이유
- 인스턴스가 오직 하나만 있을 때 유용하게 사용할 수 있음
- 여러 번 생성하지 않아 자원을 절약할 수 있음
- 글로벌 변수 사용을 줄이고, 코드를 관리하기 편해짐
예제 코드
public static void main(String[] args) {
클래스 클래스1 = new 클래스();
클래스 클래스2 = new 클래스();
System.out.println(클래스1 == 클래스2); // false
}
이렇듯 new 클래스();를 사용해 객체를 만들면 싱글톤 패턴을 만족할 수 없다.
그렇다면 new를 사용해 생성자를 만들지 못하게 하려면 private 생성자를 만들면 된다.
public class 클래스 {
private 클래스() {}
}
이 클래스 밖에서는 더이상 밖에서는 new를 사용해 인스턴스를 만들 수 없게되기 때문에 인스턴스에 글로벌하게 접근할 수 있는 방법을 제공해야한다.
public class 클래스 {
private 클래스() {}
public static 클래스 getInstance() {
return new 클래스();
}
}
public static void main(String[] args) {
클래스 클래스1 = 클래스.getInstance();
클래스 클래스2 = 클래스.getInstance();
System.out.println(클래스1 == 클래스2); // false
}
문제는 getInstance() 메서드는 new 클래스(); 로 인스턴스를 생성하기때문에 매번 다른 객체가 생성된다.
인스턴스가 만들어지지 않았다면 그때 만들어주고, 이미 만들어져 있다면 그대로 리턴하게 만들면 된다.
public class 클래스 {
private static 클래스 instance;
private 클래스() {}
public static 클래스 getInstance() {
if (instance == null) {
instance = new 클래스();
}
return instance;
}
}
public static void main(String[] args) {
클래스 클래스1 = 클래스.getInstance();
클래스 클래스2 = 클래스.getInstance();
System.out.println(클래스1 == 클래스2); // true
}
하지만 이러한 방식에는 멀티쓰레드 환경에서 안전하지 않다. 왜냐하면 getInstance()의 if문에 진입한 순간 다른 요청이 들어온다면 new 클래스();가 두번 호출돼서 다른 인스턴스가 생길 수 있기 때문이다.
쓰레드 safe하게 구현하는 법을 알아보자
1. synchronized 키워드 사용하기 - 락 매커니즘을 이용하기 때문에 성능상 잘 사용하지 않는 방법
public class 클래스 {
private static 클래스 instance;
private 클래스() {}
public static synchronized 클래스 getInstance() {
if (instance == null) {
instance = new 클래스();
}
return instance;
}
}
2. 미리 객체를 만들어두는 방법 - 이른 초기화(eager initialization), 클래스가 로딩되는 시점에 미리 초기화를 시켜놓기 때문에 멀티쓰레드 환경에서 안전하게 실행된다.
public class 클래스 {
private static final 클래스 INSTANCE= new 클래스();
private 클래스() {}
public static 클래스 getInstance() {
return INSTANCE;
}
}
그러나 이 방법도 인스턴스를 만드는데 메모리를 사용하기 때문에 이 인스턴스를 사용하지 않는다면 낭비가 된다.
3. double checked locking기법 사용하기
public class 클래스 {
private static volatile 클래스 instance; // volatile 자바 1.5부터 동작함
private 클래스() {}
public static 클래스 getInstance() {
if (instance == null) {
synchronized (클래스.class) { //동시에 요청이 들어와도 순서를 기다림
if (instance == null) {
instance = new 클래스();
}
}
}
return instance;
}
}
이 방법은 “동시 요청에 안전하지 않다.” 의 문제를 synchronized 블록으로 먼저 들어온 요청이 끝날때까지 이후에 들어온 요청을 기다리게 만드는 방법이다.
이 방법은 1번 방법의 getInstance()의 호출시점 마다 synchronized 가 걸리지 않기 때문에 성능의 문제를 개선하고, 2번 방법처럼 로딩 시점에 인스턴스를 생성하지 않고, 필요한 시점에 호출한다는 점이다.
그러나 volatile 는 자바 1.5부터 동작하고, 코드가 복잡하다는 단점이 있다.
4. static inner 클래스 사용하기 - 권장하는 방법
public class 클래스 {
private 클래스() {}
private static class 내부스태틱클래스{
private static final 클래스 INSTANCE = new 클래스();
}
public static 클래스 getInstance() {
return 내부스태틱클래스.INSTANCE;
}
}
멀티쓰레드 환경에서 안전하고, getInstance(); 가 호출 되는 시점에 내부클래스가 로딩되며 만들기 때문에 Lazy로딩도 가능한 코드가 된다.
5. enum으로 구현하기
public enum 클래스 {
INSTANCE;
}
public static void main(String[] args) {
클래스 클래스1 = 클래스.INSTANCE;
클래스 클래스2 = 클래스.INSTANCE;
System.out.println(클래스1 == 클래스2); // true
}
enum 으로도 싱글톤을 구현할 수 있지만, 2번 방법처럼 로딩시점에 미리 만들어지고 상속을 쓰지 못한다.