2025-03-27 singleton

성현규·2025년 3월 27일
post-thumbnail

Singleyon pattern

1. 싱글톤 log mannager 문제

  • public static synchronized LogManager getInstance() 이런식으로 멀티스레드때의 위험을 피할 수 있다.
public class LogManagerMain {
    public static void main(String[] args) {
        ModuleA a = new ModuleA();
        ModuleB b = new ModuleB();
        ModuleC c = new ModuleC();

        a.run();
        b.run();
        c.run();
    }
}



    class LogManager{
        private static LogManager logManager;  // 매서드 영역에 객체 주소를 저장할 유일한 참조용 변수 생성

        private LogManager(){}                 // class밖에서 객체를 생성하지 못하도록 block

        public static LogManager getInstance(){
            if (logManager == null){
                logManager = new LogManager();
                System.out.println("logmanager 객체가 생성되었습니다.");
            }

            return logManager;
        }

        public void log(String message){
            System.out.printf("[log] Module%s 실행\n", message);
        }
    }

    interface Module {
        public void run();  
    }


    class ModuleA implements Module{
        public void run(){
            LogManager.getInstance().log("A");
        }
    }

    class ModuleB implements Module{
        public void run(){
            LogManager.getInstance().log("B");
        }
    }

    class ModuleC implements Module{
        public void run(){
            LogManager.getInstance().log("C");
        }
    }

개선

  • 멀티 스레드 환경에서 LogManager의 초기화와 getInstance의 초기화 시점이 엇갈리면서 get매서드가 클래스 초기화시에 하나의 스레드만 처리할 수 있게 도와주는 락의 이득을 못봄. 하여 내부정적 클래스를 만듦으로 내부 클래스의 초기화와 get매서드의 초기화가 같이 일어날 수 있게 함으로서 하나의 스레드만 안전하게 통과시킬 수 있음.
  • 내부 클래스는 외부 클래스 초기화시 정의(내가 있다!, 메모리 할당할 준비해라)만 되고 초기화는 되지 않아 사용시점에 같이 초기화 시킬 수 있음.
  • final은 변수가 한번만 초기화되는 것을 보장함. 다른 객체로 대체되지 않음.
  • static이 아닌 인스턴스 매서드는 객체의 고유한 상태에 따라 다르게 동작할 수 있도록 설계되어 객체없이는 사용할 수 없다. 즉 누군가가 해야한다고 설계도에 적어둔 느낌이다. static은 설계도와 함께 존재하며 설계도를 들여다보면 보이는 당연한 사실 같은 것이다.
  • 자동차 설계도가 주행(인스턴스 매서드드)을 할 수 있는건 아니지만 설계도에 적혀있는 비밀(static 매서드드)은 들여다보면 누구든 알 수 있다.
class LogManager{
    private static class Holder{ // 정적 내부 클래스: Holder가 직접 호출되기 전까지 클래스의 로딩을 막음. Lazy loading -> 혹여나 LogManager가 다른데에서 호출되더라도 Holder는 여전히 호출되지 않음.
        private static final LogManager logManager = new LogManager(); // static final이면 한번 초기화된 이후 절대 다른 값으으로 바뀌지 않음.
    }

    private LogManager(){}                 

    public static LogManager getInstance(){
        return Holder.logManager;
    }

    public void log(String message){
        System.out.printf("[log] Module%s 실행\n", message);
    }
}

interface Module {
    public void run();  
}


class ModuleA implements Module{
    public void run(){
        LogManager.getInstance().log("A");
    }
}

class ModuleB implements Module{
    public void run(){
        LogManager.getInstance().log("B");
    }
}

class ModuleC implements Module{
    public void run(){
        LogManager.getInstance().log("C");
    }
}

2. 싱글톤 심화: 서버 연결 상태 관리 시스템

  • start()는 내부적으로 OS에 "새로운 스레드 시작해줘!"라고 요청하는 구조임
  • Thread는 실제로 작업을 실행해줄 실행기(executor)이고, Runnable은 실행할 작업(job)을 담고 있는 설계도이다다.
package com.design_pattern_trainning;

public class Main {
    public static void main(String[] args) { // main 매서드 까먹지 말기기
    Thread a = new Thread(new ModuleX());
    Thread b = new Thread(new ModuleY());
    Thread c = new Thread(new ModuleZ());


    a.start();
    b.start();
    c.start();
    }
    
}

class ConnectionManager{
    private ConnectionManager(){} // 밖에서 ConnectionManager의 객체 생성 불가능
    private static boolean connected = false; //

    private static class Holder{ // 외부에서 접근하게 할 거 제외하고는 전부 private
        private static final ConnectionManager connection = new ConnectionManager();
    }

    public static ConnectionManager getInstance(){
        return Holder.connection;
    }

    public synchronized void connect(){ // connect와 disconnect가 중복해서 호출되므로 동기화해서 동시에 실행되지 않도록 한다.
        if (connected){
            System.out.println("이미 연결되어 있습니다.");
        }

        else {
            System.out.println("서버에 연결합니다.");
            connected = true;
        }
    }

    public synchronized void  disconnect(){
        if (connected){
            System.out.println("서버연결을 종료합니다.");
            connected = false;
        }

        else {
            System.out.println("이미 연결이 해제된 상태입니다.");
        }
    }

    public void getStatus(){  // 현재 Thead의 이름을 출력할 수 있다.
        String status = connected ? "연결됨." : "연결되어있지 않음.";
        System.out.println(Thread.currentThread().getName() + ": 현재 상태 → " + status);
    }
}

class ModuleX implements Runnable{ // **이렇게 쓰는게 가독성도 좋고 유지보수할때도 manager에 할당한 객체만 바꾸면 되니까 더 좋다. 여러번 반복해서 쓰이면 한번만 정의해두고 반복하는게 유지보수가 편하다. 인터페이스 만드는 것처럼.**
    public void run(){
        ConnectionManager manager = ConnectionManager.getInstance();
        manager.getStatus();
        manager.connect();
    }
}

class ModuleY implements Runnable{
    public void run(){
        ConnectionManager manager = ConnectionManager.getInstance();
        manager.getStatus();
        manager.connect();
    }
}

class ModuleZ implements Runnable{
    public void run(){
        ConnectionManager manager = ConnectionManager.getInstance();
        manager.getStatus();
        manager.disconnect();
    }
}
profile
학생

0개의 댓글