싱글톤 패턴을 구현하는 7가지 방법 #1. 기본, synchronized, static

HS K·2023년 2월 11일
0
post-custom-banner

1. 단순한 메서드 호출

싱글톤 패턴 생성 여부를 확인하고 싱글톤이 없으면 새로 만들고, 있다면 만들어진 인스턴스를 반환한다.


1. Java

public class Singleton {
    private static Singleton instance; //인스턴스 선언
    
    private Singleton() {
	
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
/*singleton instance를 필요로 하는 모듈이 getInstance()라는 메서드를 호출하게 될때, 
instance가 할당되지 않았을땐 instance를 생성하고 반환한다.*/

그러나 이 코드는 메서드의 원자성이 결여되어 있다. 멀티스레드 환경에서는 싱글톤 인스턴스를 2개 이상 만들 수 있다.

※ 원자성 : 여러개의 쓰레드가 있을 때, 특정 시점에 어떤 메소드를 두개 이상의 쓰레드가 동시에 호출 못한다는 것


java는 멀티 스레드 언어이기 때문에, 여러개 스레드에서의 어떠한 로직을 생각해야한다. 그래서 여러개 스레드로 발생할 상황을 생각해야한다.

Thread1에서 if instance==null → instance 생성 → instance==null! → instance 리턴


(빨간글씨가 순서니 빨간글씨만 보기)

if instance==null → thread2에서 if instance==null → instance가 thread2에서 할당 → instance 할당

위의 경우대로 전개될시, 인스턴스가 2개가 만들어진다.


thread.java

public class YunhaSync {
    private static String yunha = "오르트구름";
    
    public static void main(String[] agrs) {
        YunhaSync a = new YunhaSync();
        //Thread1 
        new Thread(() - > {
            for (int i = 0; i < 10; i++) {
                a.say("사건의 지평선");
            }
        }).start();
        
        new Thread(() - > {
            for (int i = 0; i < 10; i++) {
                a.say("오르트 구름");
            }
        }).start();
    }
    
    public void say(String song) {
        yunha = song;
        try {
            long sleep = (long)(Math.random() * 100);
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (!yunha.equals(song)) {
            System.out.println(song + " | " + yunha);
        }
    }
}

[로직분석]
메소드 say는 매개변수 song을 받아서 yunha라는 변수에다가 song을 할당하고 랜덤시간동안 있다가 비교하는 로직이다.

① yunha==song
② sleep
③ yuhha==!song

③에서 yunha와 song이 같은지 equal 함수로 확인할때, ①과 다를게 없어서 당연히 true가 나올거라고 생각할지도 모르겠지만

thread 2개에 각각 다른 매개변수(사건의 지평선, 오르트 구름)를 기반으로 say를 호출하고 있을때 Thread1에서 yunha를 사건의 지평선을 만들었다면, sleep일때 thread2가 나타나 yunha를 오르트구름을 바꿀 수 있기 때문에 yunha==song 이 true가 아닐 수도 있다.

그래서 순서대로 실행되게 하기 위해서 synchronized 키워드를 얹으면 된다.

  • say라는 메서드를 호출할 때 lock을 걸어서 다른 스레드가 메서드에 접근하지 못하도록 만드는 것이다.

자바 컴파일러 사이트 : https://www.jdoodle.com


thread2.java

public class YunhaSync {
    private static String yunha = "오르트구름";
    public static void main(String[] agrs) {
        YunhaSync a = new YunhaSync();
        new Thread(() - > {
            for (int i = 0; i < 10; i++) {
                a.say("사건의 지평선");
            }
        }).start();
        new Thread(() - > {
            for (int i = 0; i < 10; i++) {
                a.say("오르트 구름");
            }
        }).start();
    }
    public synchronized void say(String song) {
        yunha = song;
        try {
            long sleep = (long)(Math.random() * 100);
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (!yunha.equals(song)) {
            System.out.println(song + " | " + yunha);
        }
    }
}
 public synchronized void say(String song) {
        yunha = song;
        try {
            long sleep = (long)(Math.random() * 100);
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (!yunha.equals(song)) {
            System.out.println(song + " | " + yunha);
        }
    }

//순서대로 실행이 되었기 때문에 console창에 아무것도 뜨지 않는다. 

2. synchronized

인스턴스를 반환하기 전까지 격리 공간에 놓기 위해 synchronized 키워드로 잠금을 할 수 있다. 최초로 접근한 스레드가 해당 메서드 호출시에 다른 스레드가 접근하지 못하도록 잠금(lock)을 걸어준다.

  • 스레드(thread) : 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위

이 때 getInstance() 메서드를 호출할 때마다 lock이 걸려 성능저하가 된다.

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

3. 정적 멤버

정적(static) 멤버나 블록은 런타임이 아니라 최초에 JVM이 클래스 로딩 때 모든 클래스들을 로드할 때 미리 인스턴스를 생성하는데 이를 이용한 방법이다.

  • JVM : Java Virtual Machine

클래스 로딩과 동시에 싱글톤 인스턴스를 만든다. 그렇기 때문에 모듈들이 싱글톤 인스턴스를 요청할 때 그냥 만들어진 인스턴스를 반환하면 되는 원리이다.

하지만 이는 싱글톤 인스턴스가 필요없는 경우도 무조건 싱글톤 클래스를 호출해 인스턴스를 만들어야 하기 때문에 불필요한 자원낭비를 하게된다.

3.java

 public class Singleton {
     private final static Singleton instance = new Singleton();
     private Singleton() {}
     public static Singleton getInstance() {
         return instance;
     }
 }

getInstance() 메서드를 통해서 할당하는게 아니라, 처음부터 private final static Singleton instance = new Singleton();에 할당을 한다.

따라서 모듈들이 필요할때 호출하는게 아니라, 클래스 로드에 의해 최초에 생성을 해버린다.

4. 정적 블록

4.java

public class Singleton {
    private static Singleton instance = null;
    static {
        instance = new Singleton();
    }
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}
profile
주의사항 : 최대한 정확하게 작성하려고 하지만, 틀릴내용이 있을 수도 있으니 유의!
post-custom-banner

0개의 댓글