단일 스레드 환경 - 공통 로그 파일에 모든 사용자 계좌의 입금/출금의 발생 내역을 기록하는 프로젝트
단일 스레드 환경에서 제대로 실행된 Lazy Initialization 싱글톤 패턴을 다중 스레드 환경에도 적용해보자.
실행결과는
2020-07-21 at 12:22:20 KST : owner : insang7 withdraw 177027
2020-07-21 at 12:22:20 KST : owner : insang7 deposit 3855
위와 같이 나오지만 하나의 Logger가 아닌 Logger가 여러 개 만들어지는 것을 확인할 수 있다.
이유가 무엇일까? 스레드 경합 때문이다.
스레드 경합?
Logger 인스턴스가 아직 생성되지 않았을 때 스레드 1이 getInstance()의 if문을 실행하여 이미 인스턴스가 생성되었는지 확인한다.
현재 instance 변수는 null인 상태다.
만약 스레드 1이 생성자를 호출해 인스턴스를 만들기 전 스레드 2가 if문을 실행 해 instance 변수가 null인지 확인한다.
현재 null이므로 인스턴스를 생성하는 코드, 즉 생성자를 호출하는 코드를 실행하게 된다.
스레드 1도 스레드 2와 마찬가지로 인스턴스를 생성하는 코드를 실행하게 되면 결과적으로 instance 클래스의 인스턴스가 2개 생성된다.
따라서 자바에서는 Synchronized라는 키워드를 통해 이러한 상황을 막아준다!
Synchronized로 동기화
이렇게 한 후 실행을 하면 하나의 Logger 인스턴스만 생성되는 것을 확인할 수 있다.
하지만 이 방식은 getInstance() 함수 자체가 Synchronized로 묶여있기 때문에 모든 스레드가 인스턴스가 있는지 확인하고 기다리고 이 방식을 반복해야 하기 때문에 효율적이지 못하다.
DCL(Double Checked Locking) 방식을 이용해 인스턴스가 null인 경우에만 locking되게끔, null이 아닌 경우 기다리지 않고 instance를 반환하게끔 만들면 효율적일 것이다.
DCL(Double Checked Locking)
여기에서 미묘한 방식으로 에러가 뜰 수 있다.
why?
1. Logger 인스턴스를 위한 메모리 할당 2. 생성자를 통한 초기화 3. 할당된 메모리를 instance 변수에 할당
이 방식이 명령어 reorder를 통한 최적화 수행이 되어
1. Logger 인스턴스를 위한 메모리 할당 2. 할당된 메모리를 instance 변수에 할당 3. 생성자를 통한 초기화
이러한 방식으로 실행된다면 아래와 같은 문제가 생긴다.
Logger 인스턴스가 아직 생성되지 않았을 때 쓰레드 1이 getInstance 메서드의 if문을 실행해 이미 인스턴스가 생성되었는지 확인
현재 instance 변수는 null인 상태다.
Logger 인스턴스를 위한 메모리 할당
할당된 메모리를 instance 변수에 할당
쓰레드 2가 getInstance 메서드의 if문 실행하여 instance 변수가 설정된 것을 확인하고 생성된(그러나 아직 초기화되지 않은) 인스턴스를 반환 받음
스레드 2가 로깅하려고 하면 문제가 발생
따라서 아래와 같이 Volatile를 사용하여 명령어 reorder 금지를 시켜야한다.
Demand(Lazy) Holder
- volatile이나 synchronized 키워드 없이도 동시성 문제를 해결
something 클래스 안에 inner 클래스로 LazyHolder - Something 인스턴스 반환
'아키텍처+디자인패턴' 카테고리의 다른 글
DIP (Dependency Inversion Principle) (2) | 2022.07.14 |
---|---|
ReactorKit Framework (0) | 2022.06.09 |
Delegate 패턴 (0) | 2022.02.09 |
MVC, MVVM (0) | 2021.11.13 |
단일스레드 환경에서 싱글톤 패턴: Eager Initialization / Lazy Initialization (0) | 2021.10.20 |