ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 싱클톤(Singleton)패턴 구현2
    Web 개발/디자인 패턴 2023. 6. 17. 13:19

    싱글톤 패턴 구현 방법1

    1.private 생성자에 static 메서드

     public static Settings getInstance(){
            if(instance == null){
                instance = new Settings();
            }
    
            return  instance;
        }

    해당 어플리케이션에 A쓰레드와 B쓰레드가 있다고 가정했을때  A쓰레드가 위코드의 if문을 평가해서 true가 나왔다고 가정했을 때  A쓰레드가 if문을 통과해 괄호안에 진입한 순간("instance"변수에 Settings 인스턴스를 생성하여 대입하기 직전)

    B쓰레드도 A쓰레드가 Settings 인스턴스를 만들기 직전 if문을 평가하고 if문 안으로 들어 올 수 있다.

     

    그럼 결과 적으로 A쓰레드도 new Setting()를 실행할 것이고, B쓰레드도 new Settings()를 실행할 것이다. 그렇게 된다면 A쓰레드와 B쓰레드가 서로 다른 인스턴스를 가지게된다. 그래서 다른 쓰레드 safe한 방법으로 인스턴스를 생성해야한다.

     

    2.sychronized 키워드 사용하기

    public static synchronized Settings getInstance(){
            if(instance == null){
                instance = new Settings();
            }
    
            return  instance;
        }

    sychronized라는 키워드를 사용해서 getInstance 메서드를 동기화 시키는 것이다. sychronized라는 키워드를 사용하면 해당 메서드에 하나의 쓰레드만 들어올 수 있게 된다. 즉 동시에 여러 쓰레드가 해당 메서드로 들어올 수 없다. 그러므로 멀티 쓰레드 환경에서도 하나의 인스턴스만을 생성하는 것을 보장할 수 있다.

     

    다만 이 방법의 단점은 getInstance()메서드를 호출할 때마다 동기화 처리하는 작업 때문 성능에 불이익이 생길 수 있다는 점이다. 동기화 처리라는 방법 자체가 lock을 사용한다. key가 되는 lock을 잡아 해당 key를 가지고 있는 쓰레드만(lock을 가지고 있는 쓰레드) 해당 메서드에 접근할 수 있도록 하는 방법이기 때문이다. 다 쓰고난 lock은 해제된다. 때문에 부과적인 성능부하가 생길 여지가 있다.

     

    3.이른 초기화(eager initializaion)사용하기

    public class Settings {
    
        public static final Settings INSTANCE = new Settings();
    
        private Settings(){};
    
        public static Settings getInstance(){
            return  INSTANCE;
        }
    }

    final 키워드를 붙이고 변수명 대문자로 변경해 상수처럼 사용한다. 이 방법은 스레드 safe하다. 왜냐하면 getInstance메서드에 여러 쓰레드가 들어오더라 이미 선언된 INSTANCE를 return 해주기만 하기 때문이다.

     

    그리고 이른 초기화된 INSTANCE Settings 클래스가 로딩되는 시점 초기화가 된다. 즉 프로그램이 작동하기전에 미리 만들어 두는 것이다. 

     

    다만 단점은 미리만들어둔 다는 그자체가 단점이 될 수 있다. 만약 해당하는 인스턴스를 만드는 과정 자체가 길고 복잡하며 메모리를 많이 사용한다면 프로그램을 로딩하는데만 엄청난 리소스를 할애해야하기 때문이다.

     

    4.double checked locking 사용하기

    double checked locking으로 효율적인 동기화 블럭 만들기

    public class Settings {
    
        public static volatile Settings instance;
    
        private Settings(){};
    
        public static Settings getInstance(){
            if(instance == null){
                synchronized (Settings.class){
                    if(instance == null){
                        instance = new Settings();
                    }
                }
            }
    
            return instance;
        }
    }

    synchronized 키워드를 getInstance()메서드에 사용하는 것이아니라 if문으로 instance가 null이라는 것을 한번 체크 한다음에 첫번째 if문 블럭안에 Settings.class 자체를 lock으로 사용하게 한뒤 그 안에서 if문으로 instance가 null이라는 것을 검사한다음에 instance안에 Settings의 인스턴스를 생성해서 넣어준다.

     

    A쓰레드와 B쓰레드가 있다고 가정하면 우선 A쓰레드가 첫번째 if문을 체크하고 첫번째 블럭으로 들어 왔는 (synchronized 블럭이 끝나기 전)그 때 마침 B쓰레드도 첫번째 if문을 체크하고 첫번째 블럭으로 들어 왔다면 그때는 B쓰레 synchronized블럭 안으로 들어올수 없다. 왜냐하면 이미 A쓰레드가 syncronized블럭 안에 들어와 있기 때문이다. B쓰레드는 A쓰레드가 synchromized블럭을 나갈때 까지 기다려야 하고 B쓰레드가 기다릴 동 A 쓰레드는 instance에 Settings 인스턴스를 만들어서 대입하고 return한다. 그다음에 B쓰레드가 syncronized블록안에 들어오는데 그때는 이미 instance가 null이 아니므 B 쓰레드는 새로운 인스턴스를 만드는 과정을 스킵하고 이미 만들어져 있는 instance를 return하기만 한다.

     

    Settings instance를 선언할때 volatie이라는 키워드를 적어주어야한다.

     

    double checked locking방법이 synchronized 키워드 메서드에 사용한 것보다 더 효율적인 방법이 되는 이유는getInstance()라는 메서드를 호출할 때마다 매번 synchronized가 걸리지는 않기 때문이다. 왜냐하면 이미 instance가 존재하는 경우 첫번째 if문에서 걸러지기 때문이다. 즉 먼저 들어간 쓰레드가 존재하는데 그 먼저들어가 쓰레드가 instance에 인스턴스를 생성해서 넣어주기전에 또다른 쓰레드가 들어와서 첫번째 if문을 통과해 두개의 쓰레드가 다른 instance를 가지게될 상활이 발생할때만 synchronized가 걸리기 때문이다.앞서 서술한 일이 일어나는 경우는 드물고 즉 드문 경우에만 synchronized가 걸린다.

     

    또 다른 장점은 인스턴스를 필요로하는 시점에 만들수 있다는 것이다.

     

    5.static inner 클래스 사용하기

    public class Settings {
    
        private Settings(){};
    
        private static  class SettingsHolder{
            private static final Settings INSTANCE = new Settings();
        }
    
        public static Settings getInstance(){
            return SettingsHolder.INSTANCE;
        }
    }

    Settings 클래스 안에 private static 클래스인 SettingsHolder를 하나 만들어주고 그안에 private static final로 Setting 타입의 인스턴스를 하나 초기화 해준다. 그리고 getInstance()메서드를 호출 할때 SettingsHolder가 가진 INSTANCE를 return해준다.

     

    이렇게 하면 멀티쓰드환경에서 쓰레드 safe하고 getInstance()메서드가 호출이 될때 SettingsHoler 클래스가 로딩이되므로 lazy로딩도 방지 할 수 있는 코드가 된다. double checked locking처럼 코드와 이론적배경이 복잡하지도 않다.

     

    [출처 - https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard]

     

    댓글

Designed by Tistory.