public class InitializationExample {
//즉시 초기화, 이 값을 바로 메모리에 할당
//하지만, 이 필드를 잘 사용하지 않는다면 메모리 낭비가 될 수 있다.
private Resource resource = new Resource();
public Resource getResource() {
return resource;
}
}
지연 초기화
public class LazyInitializationExample {
private Resource resource; //선언만 해두고
//getter를 통해 실제로 해당 필드를 가져올 때 값이 null이면 할당해준다
public Resource getResource() {
//다만 해당 필드를 가져올때마다 계속 if문을 통한 검사로직이 필요 => 추가비용 발생
if (resource == null) {
resource = new Resource();
}
return resource;
}
}
public class LazyInitializationExample {
private Resource resource; //선언만 해두고
//getter를 통해 실제로 해당 필드를 가져올 때 값이 null이면 할당해준다
//동시성을 고려하여 Synchronized 키워드로 작성
public synchronized Resource getResource() {
if (resource == null) {
resource = new Resource();
}
return resource;
}
}
동시성 문제는 간단히 해결되지만 synchronized로 성능이 오히려 저하된다. 사실 초기화가 두번 이루어지는게 문제가 될 수 있는건데, 두번 초기화돼도 상관없는 상황이면 그냥 synchonized 떼고 써도됨(83-5 단일검사 관용구)
혹은, 성능에 관계없이 초기화 순환성을 피하기 위해 지연초기화를 사용하는 것이라면 그냥 이렇게 synchronized 를 사용하는게 명료한 답
지연 초기화 홀더 클래스 관용구 사용
자바에서 클래스는 클래스가 처음 쓰일 때 비로소 초기화된다는 점을 이용
public class LazyInitializationExample {
//정적 내부 클래스 사용
private static class ResourceHolder {
static final Resource INSTANCE = new Resource();
}
public Resource getResource() {
return ResourceHolder.INSTANCE;
}
}
synchronized 사용할 필요 아예 없음 => 성능저하X
클래스 초기화할 때는 VM이 알아서 필드접근에 대한 동기화 시켜줌
혹은 이중검사 관용구를 사용
public class LazyInitializationExample {
private volatile Resource resource; //선언만 해두고, volatile로!
//getter를 통해 실제로 해당 필드를 가져올 때 값이 null이면 할당해준다
//Synchronized 키워드를 메서드 내부 검사로직에 넣어준다
public Resource getResource() {
Resource result = this.resource;
//값이 이미 할당돼있는 상태면 바로 결과 반환(lock 사용X)
if (result != null) {
return result;
}
//초기화가 안돼있을때만 lock 사용
synchronized(this) {
if (result == null) {
result = new Resource();
}
return result;
}
}
}
이미 초기화된 상태에서는 synchronized가 작동하지 않으므로 성능저하가 생기지 않음
필드를 volatile로 선언해야함(아이템78 참고)
🍑 결론
지연초기화는 항상 성능테스트 해보고 써야한다.
성능 문제로 쓰는거면 지연 초기화 홀더 클래스 관용구 사용하면 좋음
코드 ㅈㄴ복잡해짐 왜 왠만하면 일반적인 초기화가 낫다고 하는지 알겠죠?
번외
초기화 순환성이란?
클래스나 객체를 초기화할 때, 둘 이상의 요소가 서로를 참조해야하는 경우 발생할 수 있음
StackOverFlow
public static class A {
private B b;
// A 객체 생성 시 A 인스턴스를 새로 만들기 때문에 문제가 발생한다.
public A() {
b = new B(new A()); // 새로운 A 인스턴스를 생성하며, 이로 인해 무한 루프가 발생
}
}
public static class B {
private A a;
public B(A a) {
this.a = a;
}
}
public static void main(String[] args) {
A a = new A(); // 무한 루프에 빠짐
}
지연 초기화를 이용한 문제 해결
public static class A {
private B b;
public A() {
// 지연 초기화를 위해 B 인스턴스 생성을 미룸
}
//두번 초기화되는 문제를 해결하기 위해 synchronized
public synchronized B getB() {
if (b == null) {
b = new B(this); // 필요 시에만 B 인스턴스 생성
}
return b;
}
}
public static class B {
private A a;
public B(A a) {
this.a = a;
}
}
public static void main(String[] args) {
A a = new A(); // 무한 루프에 빠지지 않음
B b = a.getB(); // B 인스턴스가 처음 사용될 때 생성
}
Chapter : 11. 동시성
Item : 83. 지연 초기화는 신중히 사용하라
Assignee : jseok0917
🍑 서론
지연 초기화(Lazy initialization)
일반적인 초기화 vs 지연 초기화
일반적인 초기화
지연 초기화
요약
🍑 본론
지연 초기화는 어떤 상황에 써야할까?
성능 최적화를 위해
초기화 순환성을 피하기 위해 (아래 번외 참고, 책 443페이지에 번역오류있음, https://github.com/JavaBookStudy/JavaBook/issues/45 참고)
어떻게 지연 초기화 코드를 짜야할까?
🍑 결론
번외
초기화 순환성이란?
Referenced by