1. 싱글톤 패턴??
인스턴스를 오직 한 개만 제공하기 위한 객체 생성을 위한 디자인 패턴이다.
2. 멀티쓰레드 환경에서의 동시성 문제
멀티쓰레드 환경에서는 경합 조건(race condition) 을 고려해야 한다. 특히 싱글톤의 경우에는 경합 조건에 의해 객체가 두 개 생성될 수 있다. 이를 방지하기 위해서는 동기화 메커니즘을 사용해야 하며 모니터(monitor) 기반의 synchronized 키워드를 사용해 임계 영역(critical section) 에 대해 접근을 제어할 수 있다. synchronized 을 사용 시에는 성능을 위해 임계 영역 범위를 최소화하는 것이 중요하다.
double checked locking 은 멀티스레드 환경에서 객체 생성을 최적화하는 데 사용되는 소프트웨어 디자인 패턴이다. 싱글톤 패턴과 같은 상황에서 객체가 한 번만 생성되도록 보장하면서 동시에 여러 스레드가 객체를 생성하려는 경우 발생하는 성능 저하를 방지하는 데 효과적이다.
또한, 멀티스레드 환경을 사용하는데는 volatile 을 사용을 고려해야 한다. volatile 은 메모리 가시성을 보장하는 키워드이다. 메모리 가시성은 멀티스레드 환경에서 여러 스레드가 공유 메모리에 대한 액세스와 변경 사항을 서로 얼마나 일관되게 볼 수 있는지를 나타내는 개념을 말한다. 한 스레드가 공유 변수의 값을 변경한 후 다른 스레드가 즉시 변경된 값을 읽지 못하는 경우가 발생할 수 있다. volatile 을 사용하면 공유 변수가 변경될 경우, 다른 스레드에서 인식해서 값이 변경된다. 관련 코드는 해당 링크를 참고해서 확인해보자.
class Outer {
private static volatile Outer INSTANCE;
private Outer() {}
public static Outer getInstance() {
// double checked locking
if (INSTANCE == null) {
synchronized(Outer.class) {
if (INSTANCE == null) {
INSTANCE = new Outer();
}
}
}
return INSTANCE;
}
}
3. 효율적인 메모리 관리를 위한 싱글톤 객체 활용법
애플리케이션이 실행되면서 메모리에 초기화하는 작업을 eager initialization 이라 부른다. 하지만 비용이 비싸다면 lazy initialization 을 고려해야 한다. 이는 nested static class 사용해 구현할 수 있다. JVM 의 class loader 는 loading - linking - initialization 작업을 거치는데, initialization 에서 static 필드들을 초기화한다. 하지만 중첩 클래스의 경우, 중첩 클래스를 사용할 때 공유 변수가 초기화 되므로 lazy initialization 을 구현할 수 있다.
class Outer {
private static class Inner {
private static final Outer INSTANCE = new Outer();
public static Outer getInstance() {
return INSTANCE;
}
}
public static Outer getInstance() {
return Inner.getInstance();
}
}
4. 싱글톤 구현을 깨는 방법
싱글톤 구현을 깨는 방법은 reflection 과 serialization/deserialization 이 있다.
reflection 은 자바 바이트 코드를 통해 접근하여 클래스 기반의 객체는 생성 가능하다. reflection 을 통한 객체 생성을 방지하기 위해서는 enum 을 사용하는 방법도 고려해야 한다.
serialization/deserialization 의 경우, 외부 시스템에서 애플리케이션으로 객체가 넘어오면 객체를 새로 생성하기 때문에 싱글톤을 보장할 수 없다. 이를 해결하기 위해서는 readResolve() 을 정의해야 한다. 자바 기반의 애플리케이션이 deserialization 과정에서 해당 메서드를 사용하기 때문에 해당 메서드에 싱글톤 객체를 설정하면 추가 객체 생성을 방지할 수 있다.
public final class Outer implements Serializable {
private static final Outer INSTANCE = new Outer();
private Outer() {}
public static Outer getINSTANCE() {
return INSTANCE;
}
// readResolve 메서드를 정의
private Object readResolve() {
return INSTANCE;
}
}
Reference
- [geeksforgeeks] java-program-to-demonstrate-the-lazy-initialization-thread-safe : https://www.geeksforgeeks.org/java-program-to-demonstrate-the-lazy-initialization-thread-safe/
- [baeldung] java-singleton-double-checked-locking : https://www.baeldung.com/java-singleton-double-checked-locking
- [geeksforgeeks] java-program-to-demonstrate-the-lazy-initialization-thread-safe: https://www.geeksforgeeks.org/java-program-to-demonstrate-the-lazy-initialization-thread-safe
- [geeksforgeeks] volatile-keyword-in-java : https://www.geeksforgeeks.org/volatile-keyword-in-java/
- [naver d2] jvm internal : https://d2.naver.com/helloworld/1230
- [Java] static inner class 는 언제 로드가 될까? 로드와 초기화 : https://kdhyo98.tistory.com/70
- [inflearn] 코딩으로 학습하는 GoF의 디자인 패턴
'java > summary' 카테고리의 다른 글
퍼사드 패턴(Facade Pattern) (0) | 2024.07.08 |
---|---|
커맨드 패턴(command pattern) (0) | 2024.07.08 |
책임 연쇄 패턴 (chain of responsibility pattern) (1) | 2024.07.05 |
정밀 연산에는 BigDecimal 을 사용하자 (0) | 2024.04.24 |
Garbage Collector simple summary (0) | 2024.03.04 |
1. 싱글톤 패턴??
인스턴스를 오직 한 개만 제공하기 위한 객체 생성을 위한 디자인 패턴이다.
2. 멀티쓰레드 환경에서의 동시성 문제
멀티쓰레드 환경에서는 경합 조건(race condition) 을 고려해야 한다. 특히 싱글톤의 경우에는 경합 조건에 의해 객체가 두 개 생성될 수 있다. 이를 방지하기 위해서는 동기화 메커니즘을 사용해야 하며 모니터(monitor) 기반의 synchronized 키워드를 사용해 임계 영역(critical section) 에 대해 접근을 제어할 수 있다. synchronized 을 사용 시에는 성능을 위해 임계 영역 범위를 최소화하는 것이 중요하다.
double checked locking 은 멀티스레드 환경에서 객체 생성을 최적화하는 데 사용되는 소프트웨어 디자인 패턴이다. 싱글톤 패턴과 같은 상황에서 객체가 한 번만 생성되도록 보장하면서 동시에 여러 스레드가 객체를 생성하려는 경우 발생하는 성능 저하를 방지하는 데 효과적이다.
또한, 멀티스레드 환경을 사용하는데는 volatile 을 사용을 고려해야 한다. volatile 은 메모리 가시성을 보장하는 키워드이다. 메모리 가시성은 멀티스레드 환경에서 여러 스레드가 공유 메모리에 대한 액세스와 변경 사항을 서로 얼마나 일관되게 볼 수 있는지를 나타내는 개념을 말한다. 한 스레드가 공유 변수의 값을 변경한 후 다른 스레드가 즉시 변경된 값을 읽지 못하는 경우가 발생할 수 있다. volatile 을 사용하면 공유 변수가 변경될 경우, 다른 스레드에서 인식해서 값이 변경된다. 관련 코드는 해당 링크를 참고해서 확인해보자.
class Outer {
private static volatile Outer INSTANCE;
private Outer() {}
public static Outer getInstance() {
// double checked locking
if (INSTANCE == null) {
synchronized(Outer.class) {
if (INSTANCE == null) {
INSTANCE = new Outer();
}
}
}
return INSTANCE;
}
}
3. 효율적인 메모리 관리를 위한 싱글톤 객체 활용법
애플리케이션이 실행되면서 메모리에 초기화하는 작업을 eager initialization 이라 부른다. 하지만 비용이 비싸다면 lazy initialization 을 고려해야 한다. 이는 nested static class 사용해 구현할 수 있다. JVM 의 class loader 는 loading - linking - initialization 작업을 거치는데, initialization 에서 static 필드들을 초기화한다. 하지만 중첩 클래스의 경우, 중첩 클래스를 사용할 때 공유 변수가 초기화 되므로 lazy initialization 을 구현할 수 있다.
class Outer {
private static class Inner {
private static final Outer INSTANCE = new Outer();
public static Outer getInstance() {
return INSTANCE;
}
}
public static Outer getInstance() {
return Inner.getInstance();
}
}
4. 싱글톤 구현을 깨는 방법
싱글톤 구현을 깨는 방법은 reflection 과 serialization/deserialization 이 있다.
reflection 은 자바 바이트 코드를 통해 접근하여 클래스 기반의 객체는 생성 가능하다. reflection 을 통한 객체 생성을 방지하기 위해서는 enum 을 사용하는 방법도 고려해야 한다.
serialization/deserialization 의 경우, 외부 시스템에서 애플리케이션으로 객체가 넘어오면 객체를 새로 생성하기 때문에 싱글톤을 보장할 수 없다. 이를 해결하기 위해서는 readResolve() 을 정의해야 한다. 자바 기반의 애플리케이션이 deserialization 과정에서 해당 메서드를 사용하기 때문에 해당 메서드에 싱글톤 객체를 설정하면 추가 객체 생성을 방지할 수 있다.
public final class Outer implements Serializable {
private static final Outer INSTANCE = new Outer();
private Outer() {}
public static Outer getINSTANCE() {
return INSTANCE;
}
// readResolve 메서드를 정의
private Object readResolve() {
return INSTANCE;
}
}
Reference
- [geeksforgeeks] java-program-to-demonstrate-the-lazy-initialization-thread-safe : https://www.geeksforgeeks.org/java-program-to-demonstrate-the-lazy-initialization-thread-safe/
- [baeldung] java-singleton-double-checked-locking : https://www.baeldung.com/java-singleton-double-checked-locking
- [geeksforgeeks] java-program-to-demonstrate-the-lazy-initialization-thread-safe: https://www.geeksforgeeks.org/java-program-to-demonstrate-the-lazy-initialization-thread-safe
- [geeksforgeeks] volatile-keyword-in-java : https://www.geeksforgeeks.org/volatile-keyword-in-java/
- [naver d2] jvm internal : https://d2.naver.com/helloworld/1230
- [Java] static inner class 는 언제 로드가 될까? 로드와 초기화 : https://kdhyo98.tistory.com/70
- [inflearn] 코딩으로 학습하는 GoF의 디자인 패턴
'java > summary' 카테고리의 다른 글
퍼사드 패턴(Facade Pattern) (0) | 2024.07.08 |
---|---|
커맨드 패턴(command pattern) (0) | 2024.07.08 |
책임 연쇄 패턴 (chain of responsibility pattern) (1) | 2024.07.05 |
정밀 연산에는 BigDecimal 을 사용하자 (0) | 2024.04.24 |
Garbage Collector simple summary (0) | 2024.03.04 |