공부하고 기록하는, 경제학과 출신 개발자의 노트

학습일지/Language

[Design Pattern] Singleton

inspirit941 2020. 12. 11. 19:55
반응형

SingleTon

객체가 많아지면 리소스 사용이 많아지고, 리소스가 많으면 프로그램 전체 속도가 떨어진다.


-> 생성할 수 있는 객체의 최대 개수를 제한할 필요가 생김

Singleton 패턴: 객체 생성을 단 한번만 허용하는 패턴.

  • 객체의 생성개수 제한이라는 조건이, 객체를 활용하려는 쪽에서 일일이 신경쓰지 않아도 되도록 하는 게 핵심.

용례

  • DB Connection Pool
  • Log Writer

...

@Getter
public class Database {
    private static Database singleton;
    private String name;

    // getInstance()로 객체 불러오기.
    // 객체가 없을 경우 singleton 변수에 새 인스턴스를 할당하고, 
    // 있으면 해당 객체를 그대로 리턴한다.
    public synchronized static Database getInstance(String name) {

        // 여러 쓰레드가 한 번에 접근할 때 - 순차적인 접근을 위해 synchronized 키워드 사용.

        if (singleton == null) singleton = new Database(name);
        return singleton;
    }
    // 생성자를 외부에서 호출할 수 없도록 설정.
    private Database(Strign name){
        try {
            Thread.sleep(1000);
            this.name = name;
        } catch(Exception e){}
    }


}

public class TestSingleton1 {
    public static void main(String[] args) {
        Database database;
        database = Database.getInstance("1");
        System.out.println(database.getName()); // return 1

        database = Database.getInstance("2");
        System.out.println(database.getName()); // return 1
    }
}

public class TestSingleton2 {
    static int nNum = 0;
    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                nNum ++;
                Database database = Database.getInstance(nNum + "번째 DB");
                System.out.println(database.getName());
            } catch (Exception e) {}
        };
        for (int i = 0; i<10; i++) {
            Thread t = new Thread(task);
            t.start();
        }
        /*
            이렇게 되면, "1 ~ 10번째 DB" 중 여러 개가 랜덤 생성된다. 거의 10개 다 생성되지만, 10개가 아닐 수도 있음.
            즉 싱글톤 패턴이지만, 각 쓰레드마다 전부 새 객체를 생성한 것.
            여러 쓰레드가 동시에 getInstance()를 요청 -> 요청 시점에서 singleton객체는 null이기 때문.

            getInstance() 메소드에 synchronized 키워드를 붙일 경우, 여러 쓰레드가 메소드를 동시에 호출해도 한 번에 하나씩만 처리됨. 따라서 한 번 생성된 싱글톤 객체가 계속 리턴된다.
        */
    }
}

단, synchronized 키워드는 비싸다. 메소드를 요청한 쓰레드를 전부 일렬로 세우기 때문에, 병목현상이 쉽게 발생하는 지점.

또한 singleton == null을 확인하는 부분은, 프로그램 전체를 봤을 때 한 번만 실행되기만 하면 된다.

메소드 호출 시마다 검사하는 건 비효율적임

 

따라서, 아래처럼 수정되는 게 보다 적합하다.

@Getter
public class Database {
    // static 특성을 활용해서, 처음 static으로 변수 할당할 때 객체를 생성.
    private static Database singleton = new Database("init");
    private String name;

    // getInstance()로 객체 불러오기.
    // static으로 객체를 생성한 채 시작하므로, null 비교할 필요가 없다
    public static Database getInstance(String name) {
        return singleton;
    }
    // 생성자를 외부에서 호출할 수 없도록 설정.
    private Database(Strign name){
        try {
            Thread.sleep(1000);
            this.name = name;
        } catch(Exception e){}
    }   
}

Log Writer

자바에서는 싱글톤을 직접 사용할 일이 많지 않음. 프레임워크 내부에서 이미 적용된 채 사용되는 경우가 많기 때문

public class LogWriter {
    // static 변수를 활용해 객체 정의 + 생성.
    private static LogWriter singleton = new LogWriter();
    // bufferedWriter로 로그 기록
    private static BufferedWriter bw;

    // private 생성자
    private LogWriter() {
        try {
            // bufferedWriter 생성
            bw = new BufferedWriter(new FileWriter("log.txt"));
        } catch (Exception e) {}
    }

    // getInstance
    public static LogWriter getInstance(){ return singleton; }

    // writer가 정상적으로 closed될 수 있도록
    @Override
    protected void finalize() {
        try {
            super.finalize();
            bw.close();
        } catch (Throwable ex) {}
    }

    // 로그 작성 메소드
    // 여러 쓰레드가 요청하더라도 하나의 파일에 작업해야 하므로 synchronized 예약어 사용
    public synchronized void log(String str) {
        try {
            // 현재날짜 / 시간
            // bw.write(LocalDateTime.now() + ":" + str + "\n");
            bw.write(str + "\n");
            bw.flush();
        } catch (Exception e)
    }
}

// 클래스 테스트용
public class TestLogWriter {
    public static void main(String[] args) {
        // 하나의 메인쓰레드에서 정상 작동하는지 확인.
        LogWriter logger;
        logger = LogWriter.getInstance();
        logger.log("test1");

        logger = LogWriter.getInstance();
        logger.log("test2");

        // 여러 쓰레드 생성 - 호출
        for (int i = 0; i < 50; i++) {
            Thread t = new ThreadSub();
            t.start();
            // 순서는 보장하지 않아도, 중복 없이 txt파일에 입력된 것을 확인할 수 있음.
        }
    }
}

public class ThreadSub extends Thread {
    int num;
    public ThreadSub(int num){ this.num = num}

    @Override
    public void run() {
        LogWriter logger = LogWriter.getInstance();
        if (num < 10) logger.log("*** 0" + num + " ***");
        else logger.log("***" + num + " ***");
    }
}
반응형

'학습일지 > Language' 카테고리의 다른 글

[Design Pattern] Builder  (0) 2020.12.16
[Design Pattern] FlyWeight  (0) 2020.12.15
Spring AOP 요약  (0) 2020.12.07
Java8 Stream - Optional - Date 정리  (0) 2020.10.28
Java Reflection 정리  (0) 2020.10.26