Title: 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 · Study-2-Effective-Java · Discussion #12 · GitHub
Open Graph Title: 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 · Study-2-Effective-Java · Discussion #12
X Title: 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 · Study-2-Effective-Java · Discussion #12
Description: 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라
Open Graph Description: 0. 들어가며 이번 아이템은 싱글턴 패턴을 안정적으로 구현하기 위한 방법들에 대한 내용입니다. 각각의 방법들을 예제 코드와 함께 알아보겠습니다. 1. 싱글턴이란? 싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. (Effective java 책 23p) '오직 하나만 생성할 수 있다' 라는 말은 '인스턴스가 하나만...
X Description: 0. 들어가며 이번 아이템은 싱글턴 패턴을 안정적으로 구현하기 위한 방법들에 대한 내용입니다. 각각의 방법들을 예제 코드와 함께 알아보겠습니다. 1. 싱글턴이란? 싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. (Effective java 책 23p) '오직 하나만 생성할 수 있다' 라는 말은 ...
Opengraph URL: https://github.com/orgs/Study-2-Effective-Java/discussions/12
X: @github
Domain: patch-diff.githubusercontent.com
{"@context":"https://schema.org","@type":"QAPage","mainEntity":{"@type":"Question","name":"아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라","text":"0. 들어가며
\n이번 아이템은 싱글턴 패턴을 안정적으로 구현하기 위한 방법들에 대한 내용입니다.
\n각각의 방법들을 예제 코드와 함께 알아보겠습니다.
\n1. 싱글턴이란?
\n\n싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.
\n(Effective java 책 23p)
\n
\n'오직 하나만 생성할 수 있다' 라는 말은 '인스턴스가 하나만 존재해야 한다' 라고 해석할 수도 있습니다.
\n즉, 싱글톤 구현을 위해서는 Java 의 런타임 환경인 JVM 에서 단 하나의 인스턴스만 생성되도록 구현해야 한다고 생각할 수 있습니다.
\n1.1 싱글턴의 특징
\n\n1. 메모리 절약
\n모든 객체는 생성되면 JVM 의 heap 메모리에 저장됩니다.
\n그러나 싱글턴 객체는 단 한번만 생성되기 때문에 최초에 생성된 단 한개의 객체만 heap 에 저장됩니다.
\n이후 모든 클라이언트는 해당 객체를 공유하기 때문에 메모리를 효율적으로 사용할 수 있다는 이점이 있습니다.
\n\n\n2. 데이터 공유
\n싱글톤으로 구현된 객체는 JVM heap 에 저장된 하나의 객체를 모든 클라이언트들이 공유하게 되는데요.
\n만약 싱글톤 객체에 공유할 정보를 넣어놓는다면 손쉽게 모든 클라이언트들이 공유할 수 있게 됩니다.
\n\n\n3. 멀티스레드 환경을 고려해야 함
\n다수의 클라이언트에서 하나의 객체, 즉 하나의 자원을 공유하는 상황이다 보니 자칫하면 치명적인 문제가 발생할 수 있습니다.
\n싱글톤 구현 시 멀티스레드를 고려하지 않으면 여러 클라이언트에서 하나의 자원에 접근하며 레이스 컨디션이 발생할 수 있습니다.
\n만약 싱글턴의 생성 로직에서 레이스 컨디션이 발생한다면, 여러개의 객체가 생성되는 일도 발생할 수 있습니다.
\n\n\n4. 싱글턴을 사용하는 클라이언트를 테스트하기 어려움
\n테스트 케이스들을 작성할 때 필연적으로 다양한 입력값, 출력값을 정의해야 합니다.
\n만약 싱글톤 클래스가 불변객체이고, 생성자를 통해서 값을 주입받는 다면 어떨까요?
\n그렇게 된다면 이 싱글톤 클래스를 사용하는 코드를 테스트할때 어려움이 생기게 됩니다.
\n이런 부분을 방지하고자 한다면 싱글톤 클래스가 특정 인터페이스를 상속하게 하는 방법이 있습니다.
\ninterface Something {\n void doSomething();\n}\n\nclass SingletonSomething implements Something {\n\n @Override\n public void doSomething() {\n // do something\n }\n}
\n\n2. 싱글턴을 구현하는 방법
\n\n- public static final 필드
\n- 정적 팩터리
\n- 열거 타입 활용(enum)
\n- LazyHolder 방식
\n
\n1,2,3번 방식은 Effective java 에서 제안하는 방식이고, 4번은 추가로 소개하고싶은 방법입니다.
\n1번부터 차례대로 살펴보겠습니다.
\n2.1 public static final 필드
\n/**\n * 1. public static final 필드 방식\n */\npublic class Singleton1 {\n public static final Singleton1 instance = new Singleton1();\n private Singleton1() { }\n}
\n\n- static field 는 Class loader 의 초기화 단계에서 실제로 객체가 생성됨
\n- Class loader 의 초기화 단계는 JVM 내부적으로 thread-safe 하게 동작하기 때문에 멀티스레드 환경에서도 안전함
\n- 이 싱글톤을 사용하지 않더라도 heap 에 객체가 미리 생성되어있어 불필요하게 메모리를 사용하게 됨
\n
\n2.2 정적 팩터리 방식
\n정적 팩터리 방식에서는 여러가지 방법을 사용할 수 있습니다.
\n이 방식들의 공통적인 특징은 다음과 같습니다.
\n\n- 통상적으로
getInstance 메소드는 싱글톤을 반환하는 기능을 한다고 알려져있음\n\n- 그래서 클라이언트 측에서 싱글톤을 사용한다고 인지하고 사용할 수 있어서 혼동 가능성이 적음
\n
\n \n- 그리고 상황에 따라
getInstance 메소드만 수정하면 Singleton <--> Non Singleton 으로 변경할 수 있음 \n
\n1) static final 필드 활용
\n/**\n * 2. 정적 팩터리 방식\n * 1) static final 필드 활용\n */\npublic class Singleton2_1 {\n private static final Singleton2_1 instance = new Singleton2_1();\n private Singleton2_1() { }\n\n public static Singleton2_1 getInstance() {\n return instance;\n }\n}
\n2.1 과 특징이 같습니다.
\n\n- static field 는 Class loader 의 초기화 단계에서 실제로 객체가 생성됨
\n- Class loader 의 초기화 단계는 JVM 내부적으로 thread-safe 하게 동작하기 때문에 멀티스레드 환경에서도 안전함
\n- 이 싱글톤을 사용하지 않더라도 heap 에 객체가 미리 생성되어있어 불필요하게 메모리를 사용하게 됨
\n
\n2) Lazy Initialization(지연 초기화)
\n/**\n * 2. 정적 팩터리 방식\n * 2) Lazy Initialization(지연 초기화)\n */\npublic class Singleton2_2 {\n private static Singleton2_2 instance;\n private Singleton2_2() { }\n\n public static Singleton2_2 getInstance() {\n if (instance == null) {\n instance = new Singleton2_2();\n }\n\n return instance;\n }\n}
\n1)번 방법은 사용하지 않아도 불필요하게 객체가 생성된다는 단점이 있었습니다.
\n하지만 지연 초기화 방법을 사용하면 그러한 점을 해결할 수 있습니다.
\n\n- 호출할 때 null 인 경우에 객체가 생성되므로 불필요한 메모리 사용을 줄일 수 있음
\n- 멀티스레드 환경에서 레이스 컨디션이 발생할 경우, 싱글톤을 만족하지 않을 수 있음
\n
\n3) Lazy Initialization + Synchronization(지연 초기화 + 동기화)
\n/**\n * 2. 정적 팩터리 방식\n * 3) Lazy Initialization + Synchronization(지연 초기화 + 동기화)\n */\npublic class Singleton2_3 {\n private static Singleton2_3 instance;\n private Singleton2_3() { }\n\n public static synchronized Singleton2_3 getInstance() {\n if (instance == null) {\n instance = new Singleton2_3();\n }\n\n return instance;\n }\n}
\n멀티스레드 환경에서 레이스 컨디션이 발생하는 경우를 방지하기 위해 동기화를 사용하는 방법입니다.
\nJava 의 synchronized 키워드를 이용해서 동기화 문제를 해결합니다.
\n\n- thread-safe 한 싱글톤임
\n- synchronized 키워드로 인해 성능이 저하될 수 있음
\n
\n4) LazyHolder 방식
\n/**\n * 4. LazyHolder 방식\n */\npublic class Singleton4 {\n\n private Singleton4() { }\n\n private static class LazyHolder {\n private static final Singleton4 INSTANCE = new Singleton4();\n }\n\n public static Singleton4 getInstance() {\n return LazyHolder.INSTANCE;\n }\n}
\nLazyHolder 방식은 지연 초기화 방식의 성능적 문제를 개선하기 위해 제안된 방법입니다.
\nsynchronized 키워드를 사용하면 JVM 내부적으로 락을 획득하는 작업 때문에 성능 저하가 발생합니다.
\n하지만 이 방법은 Classloader 에 Singleton4 인스턴스 생성 역할을 맡기며 지연 초기화를 구현하는 방법인데요.
\n클래스 로드와 초기화 과정은 thread-safe 하게 동작하는데, 이는 synchronized 보다는 성능이 좋다고 합니다.
\n(참고한 글 : https://medium.com/@joongwon/multi-thread-환경에서의-올바른-singleton-578d9511fd42)
\n\n- 지연 초기화로 인해 미사용 객체를 생성하지 않아 메모리를 효율적으로 사용
\n- synchronized 키워드를 사용한 방식보다는 성능상 이점이 있음
\n
\n\n개인적으로는 synchronized 키워드와 Classloader 의 thread-safe 한 동작에서 왜 성능차이가 있는건지 궁금한데요.
\n이 부분은 조금 더 알아봐야할 것 같습니다.
\n
\n3. 조금 더 생각해볼 것들
\n\n- volatile 키워드를 활용한 방식(volatile singleton)
\n- 어플리케이션 서버를 수평확장했을 때의 싱글톤 객체
\n
\n
\n\n토론하고싶으신 부분이나 질문이 있으시면 편하게 남겨주세요.
\n
","upvoteCount":3,"answerCount":3,"acceptedAnswer":{"@type":"Answer","text":"싱글턴을 만들때 3) Lazy Initialization + Synchronization(지연 초기화 + 동기화) 방식으로 멀티스레딩의 문제를 해결하기 위해 사용한 synchronized로 인한 성능의 문제를 해결하는 방법으로
\n4) LazyHolder 방식을 사용 하였는데 저도 처음 알게 되었습니다.
\n3) 문제를 해결하는 방법으로 4) 말고도 알고있는 내용으로는
\nDCL(Double Checked Locking) 방식에 대해 알고 있습니다.
\npublic class Singleton3 {\n\n private volatile static Singleton3 uniqueInstance;\n\n private Singleton3() {}\n\n public static Singleton3 getInstance() {\n if (uniqueInstance == null) {\n synchronized (Singleton3.class) {\n if (uniqueInstance == null) {\n uniqueInstance = new Singleton3();\n }\n }\n }\n return uniqueInstance;\n }\n}
\n3) 방식의 문제는 동기화가 꼭 필요한 시점은 아직 객체가 생성되지 않은 상태일 뿐 인데 모든 상황에 동기화가 일어나기 때문입니다.
\n따라서 uniqueInstance가 아직 생성되지 않은 시점에서만 synchronized를 통해 동기화를 한다면 성능을 좀 더 올릴 수 있는 것으로 압니다.
\n(객체가 생성된 이후에는 이미 만들어진 객체를 반환만 하면 됨)
\n
\n\n- 여기서 volatile 이라는 키워드가 존재하는데 해당 키워드 사용시 멀티스레딩을 쓰더라도 uniqueInstance 변수가 싱글턴 인스턴스로 초기화되는 과정이 올바르게 진행된다고 알 고 있는데 이 키워드가 어떻게 이러한 역할을 하게 되는지 같이 더 알아봤으면 좋겠습니다.
\n
\n
\n이번 아이템을 통해 싱글턴을 만드는 여러 방법에 대해 알게 되었고 싱글턴을 만들게 됨으로써 생기는 문제점을 해결하기 위해 각 방식이 생겨나게 된 이유도 알게 되었습니다.\n
\n정리 감사합니다.","upvoteCount":1,"url":"https://github.com/orgs/Study-2-Effective-Java/discussions/12#discussioncomment-4445631"}}}
| route-pattern | /_view_fragments/Voltron::DiscussionsFragmentsController/show/orgs/:org/:discussion_number/discussion_layout(.:format) |
| route-controller | voltron_discussions_fragments |
| route-action | discussion_layout |
| fetch-nonce | v2:d99dcada-aff1-2a14-163c-286395a026ed |
| current-catalog-service-hash | 9f0abe34da433c9b6db74bffa2466494a717b579a96b30a5d252e5090baea7be |
| request-id | E3C8:3EABC2:2F4FA2D:3F5C73A:696F1CFE |
| html-safe-nonce | 7a6e72d9d2e3b4b2b65308bed07acd0dc8933b81a848f90c52730ae7252a1eba |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFM0M4OjNFQUJDMjoyRjRGQTJEOjNGNUM3M0E6Njk2RjFDRkUiLCJ2aXNpdG9yX2lkIjoiNzg0NjY0MzA2MDk4NzMzNzk4MiIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | bf01ad508cf3c761f158e79564dcdb4fcac0d3ca1cca99065b004208d074ac39 |
| hovercard-subject-tag | discussion:4656697 |
| github-keyboard-shortcuts | repository,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/Voltron::DiscussionsFragmentsController/show/orgs/Study-2-Effective-Java/12/discussion_layout |
| twitter:image | https://opengraph.githubassets.com/57c4610a174c6c75385f14fd697045bd8eb6f3d54dc41c00c373eb5ff070cb9a/orgs/Study-2-Effective-Java/discussions/12 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/57c4610a174c6c75385f14fd697045bd8eb6f3d54dc41c00c373eb5ff070cb9a/orgs/Study-2-Effective-Java/discussions/12 |
| og:image:alt | 0. 들어가며 이번 아이템은 싱글턴 패턴을 안정적으로 구현하기 위한 방법들에 대한 내용입니다. 각각의 방법들을 예제 코드와 함께 알아보겠습니다. 1. 싱글턴이란? 싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. (Effective java 책 23p) '오직 하나만 생성할 수 있다' 라는 말은 '인스턴스가 하나만... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | b278ad162d35332b6de714dfb005de04386c4d92df6475522bef910f491a35ee |
| turbo-cache-control | no-preview |
| octolytics-dimension-user_id | 120388640 |
| octolytics-dimension-user_login | Study-2-Effective-Java |
| octolytics-dimension-repository_id | 577325341 |
| octolytics-dimension-repository_nwo | Study-2-Effective-Java/Effective-Java |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 577325341 |
| octolytics-dimension-repository_network_root_nwo | Study-2-Effective-Java/Effective-Java |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 39aed5006635ab6f45e6b77d23e73b08a00272a3 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width