백엔드/Java

자바 싱글톤(Singleton) 패턴

dami97 2023. 5. 13. 15:27
반응형

자바 싱글톤(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번 방법처럼 로딩시점에 미리 만들어지고 상속을 쓰지 못한다.

 

 

 

 

반응형