반응형
Jake Seo
제이크서 위키 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (715)
    • 일상, 일기 (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 참조 해제
  • pnpm
  • 자료구조
  • 슬로우 쿼리
  • 자바 디자인패턴
  • 메이븐 골
  • Javadoc 자바독 자바주석 주석 Comment
  • 이펙티브 자바
  • 자바스크립트 인터뷰
  • 플라이웨이트패턴
  • Next.js
  • 싱글톤
  • item9
  • MSSQL
  • try-with-resources
  • Pre-rendering
  • 메이븐 페이즈
  • next js app
  • 자바
  • 프로그래머의 뇌
  • 자바스크립트
  • NEXT JS
  • 빈 검증
  • 작업기억공간
  • 자바스크립트 면접
  • 느린 쿼리
  • 디자인패턴
  • 이펙티브자바
  • 외래키 제약조건
  • 스프링 검증
  • Java
  • 서버리스 컴퓨팅
  • 자바 검증
  • rust
  • 러스트
  • prerendering
  • 토비의 스프링
  • 이펙티브 자바 item9
  • 알고리즘
  • item8
  • 도커공식문서
  • 추상 팩터리 패턴
  • 싱글턴
  • 싱글톤 패턴
  • 팩터리 메서드 패턴
  • item7
  • bean Validation
  • 객체복사
  • 메이븐 라이프사이클
  • serverless computing

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 위키 블로그

Java/자바 디자인 패턴

싱글톤 패턴 (Singleton Pattern) 이란?

2023. 1. 20. 02:23

싱글톤 패턴이란?

  • 클래스의 인스턴스를 오직 하나만 두고 사용하는 패턴이다.
  • 1개 외에 추가적인 인스턴스 생성을 의도적으로 막아야 한다.

용례

  • 애플리케이션의 구성(configuration) 정보와 같이 런타임 내에 공유되어야 하는 정적인 정보를 가지고 있는 클래스의 인스턴스를 만들 때 이 패턴을 사용할 수 있다.
  • 생성 비용이 큰 인스턴스가 있을 때, 이 인스턴스를 한번 만들고 계속 재활용이 가능하다면 이 인스턴스를 싱글톤으로 공유하는 것을 생각해볼 수 있다.

자바에서의 나이브한 싱글톤 구현

public class SingletonClass {
  private static instance;

  private SingletonClass() {
    // 생성자를 private 으로 만들면, 외부에서 생성자 호출이 불가능하다.
  }

  public static SingletonClass getInstance() {
    // 1번이라도 호출되어야 인스턴스를 생성한다. 처음부터 싱글톤 인스턴스가 만들어져있는 것이 아니다.
    if (instance == null) {
      instance = new SingletonClass();
    }

    // static 메서드에서 static 필드를 반환하는식으로 구현한다.
    return instance;
  }
}
  • static 키워드를 활용하여 싱글톤 클래스를 작성할 수 있다.
  • static 키워드가 붙은 데이터는 Method Area(Static Area) 에 프로그램의 시작부터 상주한다.
  • 인스턴스 자체는 new 키워드를 통해 생성된 시점에 Heap Memory 에 존재하게 된다.
    • private static instance 필드는 프로그램 종료 시까지 유지되므로 싱글톤 인스턴스는 프로그램 종료시까지 가비지 컬렉팅되지 않는다.

나이브한 코드의 문제

  • 멀티 스레드 환경에서 안전하지 않다.
  • 다중 스레드가 동시에 if(instance == null) 에 접근하게 된다면, 여러 개의 SingletonClass 가 생성될 수도 있다.

멀티스레드를 고려하지 않은 스프링 개발자라면 큰 실수로 이어질 수도 있다.
흔히 보이는 스프링에서는 이용자에게 스레드 풀에서 스레드를 할당하는 방식으로 작업을 하므로 멀티스레드 작업이 자연스레 일어나니 주의해야 한다.

문제 해결 1: synchronized

public class SingletonClass {
  private static instance;

  private SingletonClass() {
    // 생성자를 private 으로 만들면, 외부에서 생성자 호출이 불가능하다.
  }

  public static synchronized SingletonClass getInstance() {
    // 1번이라도 호출되어야 인스턴스를 생성한다. 처음부터 싱글톤 인스턴스가 만들어져있는 것이 아니다.
    if (instance == null) {
      instance = new SingletonClass();
    }

    // static 메서드에서 static 필드를 반환하는식으로 구현한다.
    return instance;
  }
}
  • synchronized 키워드는 해당 메서드에 하나의 스레드만 접근함을 보장해준다.
    • lock 을 이용한다.
  • 다만, 동기로 실행되기 때문에 앞서 해당 메서드를 점유한 스레드의 작업이 끝나기 전까지는 메서드를 사용할 수 없다.
    • 이 과정에서 약간의 성능 저하가 생겨날 수 있다.

문제 해결 2: 미리 만들기

public class SingletonClass {
  private static final INSTANCE = new SingletonClass();

  private SingletonClass() {
    // 생성자를 private 으로 만들면, 외부에서 생성자 호출이 불가능하다.
  }

  public static SingletonClass getInstance() {
    return INSTANCE;
  }
}
  • 인스턴스의 초기 생성비용이 그렇게 신경쓰이지 않는다면, 이 방법도 좋은 방법이다.
  • 다만, 이렇게 만들어놓고 사용하지 않는다면 낭비인 것은 반드시 알아두어야 한다.
  • 성능 문제나 멀티스레드 문제 없이 싱글톤 패턴을 구현할 수 있다.

문제 해결 3: double checked locking 이용하기


public class SingletonClass {
  private static volatile instance;

  private SingletonClass() {}

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

    return instance;
  }
}

위 코드는 자바 1.5 이상에서만 동작한다.

설마 지금 1.4 이하를 쓰는 사람이 있을까?
  • 인스턴스가 만들어져있는지 한번 먼저 확인 후에 synchronized 블럭에서 2차로 확인하는 방식이다.
    • 장점은 매번 synchronized 가 걸리지 않는다. 인스턴스 생성 시 한번만.
  • 여러 스레드가 동시에 진입하더라도 단 하나의 스레드만 인스턴스를 만드는 것을 보장할 수 있다.
  • 이전에 메서드 자체에 synchronized 키워드를 사용한 것과 다르게 일단 한번 인스턴스화 시키면 성능상의 문제도 없다.
  • volatile 키워드까지 적어주어야 서로 다른 스레드라도 같은 메모리를 참조한다는 보장을 할 수 있다.
    • instance 를 읽어올 때 CPU 캐시에서 읽어오는 것이 아닌 메인 메모리에서 읽어옴을 보장해준다.

volatile 관련 참고 링크 1 > volatile 관련 참고 링크 2

DCLP 를 사용하지 말아야 한다는 의견 도 있는데, 이 의견은 java.concurrency 패키지에서 제공하는 mutex.lock() 때문에 일어나는 문제인 것 같다. 단순히 instance == null 까지 블로킹하는 것이 아니라 블록에서 일어나는 일이 모두 끝날 때 (초기화 작업이 모두 끝날 때) 까지 블록을 블로킹할 필요가 있다.

문제 해결 4: static inner class 사용하기

public class SingletonClass {
  private SingletonClass() {
    // 생성자를 private 으로 만들면, 외부에서 생성자 호출이 불가능하다.
  }

  private static class SingletonClassHolder {
    private static final SingletonClass INSTANCE = new SingletonClass();
  }

  public static SingletonClass getInstance() {
    return SingletonClassHolder.INSTANCE;
  }
}
  • 이 방식을 통해 간단히 getinstance() 를 실제로 호출했을 때만 생성되는 싱글톤 클래스를 만들 수 있다.
  • static inner class 는 구조상 애플리케이션 시작 시에 클래스 로더에서 초기화되지 않고, getInstance() 가 호출되었을 때 JVM 메모리에 로드되고 객체를 생성한다.

static inner class 동작의 이해를 돕는 포스팅

싱글톤을 깨는 방법

협업하는 개발자들이 대부분은 싱글톤 클래스가 만들어진 의도대로 사용할테지만, 때때로 흑마법을 사용하는 개발자들도 있다.

방법 1: Reflection 이용하기

public class App {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        SingletonClass singletonClass = SingletonClass.getInstance();

        Constructor<SingletonClass> constructor = SingletonClass.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonClass singletonClass1 = constructor.newInstance();

        System.out.println(singletonClass == singletonClass1); // false
    }
}

방법 2: 직렬화와 역직렬화 사용하기

  • 직렬화와 역직렬화는 보통 자바 클래스를 파일로 떨궜다가 다시 읽어들이는 기능을 제공한다.
  • 다른 언어와 정보를 주고받을 때도 사용할 수 있다. JSON 도 하나의 직렬화 역직렬화 기법이다.
  • Serializable 을 상속하면 된다.
public class App {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
        SingletonClass singletonClass = SingletonClass.getInstance();
        SingletonClass singletonClass1 = null;

        try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("singleton.obj"))) {
            out.writeObject(singletonClass);
        }

        try (ObjectInput in = new ObjectInputStream(new FileInputStream("singleton.obj"))) {
            singletonClass1 = (SingletonClass) in.readObject();
        }

        System.out.println(singletonClass == singletonClass1); // false
    }
}

그런데 이러한 역직렬화는 readResolve() 를 직접 구현해주는 것으로 막을 수 있다.

public class SingletonClass implements Serializable {
    // 이전과 동일

    @Serial
    protected Object readResolve() {
        return getInstance();
    }
}
  • 역직렬화 과정에서는 무조건 readResolve() 를 호출하게 되는데, 그 반환값이 getInstance() 여서 싱글톤을 유지할 수 있다.

절대 깰 수 없는 싱글톤을 만드는 방법

public enum SingletonEnum {
    INSTANCE;
    // 기본 private 생성자가 존재하여 따로 오버라이드하지 않아도 된다.
}
  • enum 을 이용한 방법은 가장 짧지만 가장 강력하다.

enum 싱글톤의 장점

  • Reflection 으로 생성자를 찾아서 억지로 생성할 수 없다.
  • 코드에는 보이지 않지만, enum 은 내부적으로 Enum 이라는 클래스를 상속하여, 직렬화 역직렬화가 가능하지만 안전장치를 해놓았다.
    • 바이트코드를 보면 보인다.
public final enum singleton/SingletonEnum extends java/lang/Enum {
  // ...
}
  • 상속하는 Enum 클래스가 비록 직렬화와 역직렬화를 제공하지만 역직렬화 시에도 자동으로 싱글톤을 보장하도록 설계되어 있다.

enum 싱글톤의 단점

  • enum 은 lazy load 가 되지 않는다는 점이 단점이다.
  • Enum 외에 다른 클래스를 상속하지 못한다.

현실세계 싱글톤의 예

자바의 Runtime 인스턴스

public class Runtime {
    private static final Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class {@code Runtime} are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the {@code Runtime} object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }
}
  • 자바 애플리케이션 시작 시에 초기화하도록 구현되어 있다.
public class App {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        System.out.println(runtime.maxMemory());
        System.out.println(runtime.freeMemory());
    }
}
  • 간단하게 메모리 등을 조회해볼 수 있다.

스프링 빈의 스코프 중 싱글톤 스코프

사실상 스프링 빈에는 singleton, prototype, request, session, application, websocket 등의 스코프가 있는데, 가장 많이 선택되는 스코프는 singleton 이다.

@Configuration
public class SpringConfig {
    @Bean
    public String singleton() {
        return "singleton";
    }
}
@SpringBootTest
class DesignPatternsWithJavaApplicationTests {
    @Test
    void contextLoads() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String hello = applicationContext.getBean("singleton", String.class);
        String hello1 = applicationContext.getBean("singleton", String.class);
        Assertions.assertThat(hello).isEqualTo(hello1);
    }
}

테스트를 잘 통과한다.

레퍼런스

백기선님 디자인 패턴 강의

반응형
저작자표시 비영리 (새창열림)

'Java > 자바 디자인 패턴' 카테고리의 다른 글

추상 팩토리 패턴 (Abstract Factory Pattern) 이란?  (0) 2023.01.24
팩토리 메서드 패턴 (Factory Method Pattern) 이란?  (0) 2023.01.23
자바 믹스인(mixins)이란?  (0) 2021.12.30
제네릭 싱글턴 팩토리  (0) 2021.12.24
플라이 웨이트 패턴  (0) 2021.12.22
    'Java/자바 디자인 패턴' 카테고리의 다른 글
    • 추상 팩토리 패턴 (Abstract Factory Pattern) 이란?
    • 팩토리 메서드 패턴 (Factory Method Pattern) 이란?
    • 자바 믹스인(mixins)이란?
    • 제네릭 싱글턴 팩토리
    Jake Seo
    Jake Seo
    ✔ 잘 보셨다면 광고 한번 클릭해주시면 큰 힘이 됩니다. ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바