티스토리 뷰
728x90
반응형
싱글톤 패턴이란?
- 싱글톤이란 어떠한 클래스가 최초 한번만 static 영역에 할당이 되어 객체가 만들어지고 여러 차례 호출이 되더라도 하나의 인스턴스를 공유하여 사용하는 디자인패턴입니다.
- 즉 하나의 객체를 공유하여 여러 사람들이 사용을 하는 것입니다.
클래스 다이어 그램
Eager Initalzation (이른 초기화 방식)
- static으로 선언하였기 때문에 클래스 로더에 의하여 클래스가 로딩될때 객체가 생성됩니다. 또한 클래스 로더에 의해 클래스가 최초 로딩될 때 객체가 생성되므로 쓰레드에 안전합니다. 하지만 싱글톤 객체의 사용 유무와 상관없이 클래스 로더에 의해 클래스가 로딩 되는 시점에 객체가 생성되므로 메모리를 잡고 있어 비효율적입니다.
public class User {
private static User instance = new User();
private User(){}
public static User getInstance() {
return instance;
}
}
public class Example {
public static void main(String[] args) {
User 홍길동 = User.getInstance();
User 이순신 = User.getInstance();
System.out.println("홍길동 = " + 홍길동);
System.out.println("이순신 = " + 이순신);
}
}
Lazy Initialization (늦은 초기화 방식)
- 아래 예제는 멀티 쓰레드 환경에서 객체의 인스턴스를 호출하는 경우 User의 객체는 하나만 만들어질 수도 있고 여러개의 객체가 만들어 질 수 있습니다. if(instance == null) 이라는 구문을 통하여 여러 쓰레드가 동시에 들어올 가능성이 존재하기 때문에 위험합니다. 이러한 멀티 쓰레드 환경에서 위험성을 대비해 Thred safe하게 만들어야할 필요성이 있습니다.
public class User {
private static User instance;
private User(){}
public static User getInstance() {
if (instance == null) {
instance = new User();
return instance;
}
return instance;
}
}
public class Example {
public static void main(String[] args) {
User 홍길동 = User.getInstance();
User 이순신 = User.getInstance();
System.out.println("홍길동 = " + 홍길동);
System.out.println("이순신 = " + 이순신);
}
}
Thread safe Lazy initialization (스레드 안전한 늦은 초기화)
- synchronized 키워드를 사용하여 여러 Thread가 접근하였을 경우 첫 번째 쓰레드만 접근하게 되고 나머지 쓰레드는 대기 상태로 됩니다. 이렇게 된다면 첫 번째 쓰레드에 의해 객체가 만들어졌기 때문에 나머지 쓰레드는 이미 만들어진 객체를 반환하게 됩니다.
하지만 여러 쓰레드는 하나의 인스턴스를 가지기 위해 synchronized 메서드를 거쳐야하며 synchronized는 성능상 부담스러운 부분이 있습니다. 왜냐하면 synchronized는 하나의 쓰레드만 접근 가능하며 다른 쓰레드는 대기 상태에 머물게 되고 오버헤드가 발생하게 됩니다.
public class User {
private static User instance;
private User(){}
public static synchronized User getInstance() {
if (instance == null) {
instance = new User();
}
return instance;
}
}
public class Example {
public static void main(String[] args) {
User 홍길동 = User.getInstance();
User 이순신 = User.getInstance();
System.out.println("홍길동 = " + 홍길동);
System.out.println("이순신 = " + 이순신);
}
}
Thread safe Lazy initialization + Double-checked locking 기법
- synchronized 키워드를 사용하게 되면 오버헤드가 발생하여 성능 저하가 발생하게 되고 이를 조금 더 완화하는 방식인 Double-checked locking 기법이 나오게 되었습니다.
- 해당 방식은 첫번째 if문에서 null인 경우만 synchronized에 접근하게 되며 두번째부터는 instance가 null이 아니므로 synchronized에 접근을 하지 않습니다. 이러한 기법을 통하여 성능 저하를 보완할 수 있습니다.
public class User {
private static User instance;
private User(){}
public static User getInstance() {
// Double-checked locking
if (instance == null) {
synchronized (User.class) {
if (instance == null) {
instance = new User();
}
}
}
return instance;
}
}
public class Example {
public static void main(String[] args) {
User 홍길동 = User.getInstance();
User 이순신 = User.getInstance();
System.out.println("홍길동 = " + 홍길동);
System.out.println("이순신 = " + 이순신);
}
}
Initialization on demand holder (Bill Pugh 및 Static Holder)
- static inner class의 특성을 이용하여 static class 이지만 바로 메모리(Static 영역)에 할당되지 않고 누군가가 getInstace() 메서드를 호출해야지만 메모리에 할당되게 됩니다.
- Initialization on demand holder 개념을 이용하여 static class는 JVM의 static initializer에 의해 초기화되고 메모리로 올라가게 되는데 최초로 ClassLoad에 의해 load될 때 loadClass메서드를 통해 올라가게 되며, 이때 내부로 synchronized가 실행이 되며
명시적으로 synchronized 키워드를 사용하지 않고도 동일한 효과를 낼 수 있습니다.
public class User {
private User(){}
public static User getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final User instance = new User();
}
}
public class Example {
public static void main(String[] args) {
User 홍길동 = User.getInstance();
User 이순신 = User.getInstance();
System.out.println("홍길동 = " + 홍길동);
System.out.println("이순신 = " + 이순신);
}
}
ClassLoader
public abstract class ClassLoader {
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
...
}
return c;
}
}
}
Inner Class에 대해
- inner class는 호출될 때 로드가 됩니다.
public class Outer {
// static 변수 선언
public static String TEST01 = "TEST01";
// Outer 클래스의 static 블록
static {
System.out.println("1 - Outer class 초기화, TEST01 = " + TEST01);
TEST01 = "This is TEST01";
}
// Inner 클래스
public static class Inner {
// Inner 클래스의 static 블록
static {
System.out.println("4 - Inner class 초기화");
}
// Inner 클래스의 static 메서드
public static String info() {
return "Inner Class info Method Call";
}
}
}
public class Example {
public static void main(String[] args) {
System.out.println("2 - TEST01 --> " + Outer.TEST01);
System.out.println("3 - Outer.Inner.class --> " + Outer.Inner.class);
System.out.println("5 - Outer.Inner.info() --> " + Outer.Inner.info());
}
}
실행 결과
1 - Outer class 초기화, TEST01 = TEST01
2 - TEST01 --> This is TEST01
3 - Outer.Inner.class --> class singletonPattern.Outer$Inner
4 - Inner class 초기화
5 - Outer.Inner.info() --> Inner Class info Method Call
각 단계에 대한 내용
- 프로그램이 실행되면서 Outer 클래스가 초기화됩니다.
- TEST01 변수에 "TEST01" 이라는 문자열이 할당됩니다.
- 클래스가 로딩되고 static 변수가 준비된 후 자동으로 static 블록이 실행됩니다. static 블록이 여러개인 경우 순차적으로 실행
- 즉 Outer 클래스 초기화 --> static 변수 초기화 --> static 블록 실행
- Main 메서드가 호출되어 Outer 클래스의 static 변수를 출력합니다.
- Main 메서드에서 Inner 클래스를 로드합니다.
- Inner 클래스는 로드되었지만 초기화는 되지 않았습니다. (메모리 모델에 참조가 있는 이유입니다.)
- Inner.info() 메서드가 출력될줄 알았지만 해당 메서드를 호출하기 전에 Inner 클래스를 초기화해야합니다. 그래서 Inner 클래스의 static 블록이 4번째 단계입니다.
- 마지막으로 Inner 클래스의 info() 메서드가 호출됩니다.
로드 그리고 초기화
1 - Outer class 초기화, TEST01 = TEST01 출력 진행 과정
Outer 클래스는 Main 메서드가 실행되기 전 초기화가 진행됩니다. 그렇기 때문에 static 변수와 static 블록이 초기화가 진행되며 호출되게 됩니다.
3 - Outer.Inner.class --> class singletonPattern.Outer$Inner 출력 진행 과정
Main 메서드에서 Outer.Inner.class를 호출하였고 해당 클래스는 메모리에 올라가 있기 때문에
class singletonPattern.Outer$Inner이라는 값이 출력됩니다.
이는 클래스가 로드되어 있는 상태를 의미합니다.
하지만 static 블록이 실행되지 않았기 때문에 해당 Inner 클래스는 로드만 됬을 뿐 아직 초기화전이라는 것을 확인할 수 있습니다.
4 - Inner class 초기화
5 - Outer.Inner.info() --> Inner Class info Method Call 출력 진행 과정
Inner 클래스가 초기화가 되고 Inner.info() 메서드가 호출됩니다.
Loading !== Initalization (로드 !== 초기화)
- JVM에서 Loading -> Linking -> Initialization 순으로 진행되며 동시에 진행되지 않습니다.
static이 올라가는 과정
- static이 JVM이 실행되자마자 올라가는 줄 알았으나 클래스가 시작할 때 초기화가 되며 올라가게 됩니다. 왜냐하면 해당 클래스에서 클래스를 통해 사용하기 위해 정의된 static이기 때문입니다. 많은 사람들은 로딩 또는 로드가 이루어진다고 말하며 Inner Class는 지연 로딩이라고 설명을 하는데 따로 분리하여 접근하는게 더 헷갈리지 않고 명확하게 이해할 수 있는거 같습니다.
참고 자료
728x90
반응형
'JAVA > Design_Pattern' 카테고리의 다른 글
[Design_Pattern] 컴포지트 패턴 (0) | 2022.03.07 |
---|---|
[Design_Pattern] 어댑터 패턴 (0) | 2022.03.05 |
[Design_Pattern] 프로토 타입 패턴 (0) | 2022.02.28 |
[Design_Pattern] 빌더 패턴 (0) | 2022.02.21 |
[Design_Pattern] 전략 패턴(Strategy Pattern) (0) | 2022.02.17 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- transactional outbox pattern spring boot
- spring boot redis 대기열 구현
- @ControllerAdvice
- microkernel architecture
- JDK Dynamic Proxy와 CGLIB의 차이
- java userThread와 DaemonThread
- space based architecture
- 자바 백엔드 개발자 추천 도서
- 공간 기반 아키텍처
- spring boot 엑셀 다운로드
- service based architecture
- pipeline architecture
- redis 대기열 구현
- spring boot redisson destributed lock
- spring boot redisson 분산락 구현
- 람다 표현식
- 트랜잭셔널 아웃박스 패턴 스프링부트
- spring boot redisson sorted set
- redis sorted set으로 대기열 구현
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- 서비스 기반 아키텍처
- spring boot excel download oom
- spring boot excel download paging
- 레이어드 아키텍처란
- java ThreadLocal
- pipe and filter architecture
- redis sorted set
- transactional outbox pattern
- spring boot poi excel download
- polling publisher spring boot
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
글 보관함