Safe Publication(안전한 게시물)이란 객체가 스레드 간에 안전하게 전파되는 것을 보장하는 것입니다: 하나의 스레드가 객체를 생성하고 초기화하면, 다른 스레드는 항상 완전히 구성된 객체를 보게 되며, 부분적으로 초기화된 상태를 보지 않습니다.
안전한 게시물이 없으면 경쟁 조건이 발생할 수 있습니다: 하나의 스레드가 객체의 초기화되지 않은 부분을 보게 될 수 있으며, 생성자가 이미 실행되었더라도 말입니다.
안전한 게시물을 보장하는 방법:
예제 (안전하지 않음):
public class Holder { private int n; public Holder(int n) { this.n = n; } } Holder holder; void publish() { holder = new Holder(42); } // 안전성이 보장되지 않음
완전히 구성되지 않은 객체가 보일 수 있습니다!
예제 (안전함):
volatile Holder holder; void publish() { holder = new Holder(42); } // holder 읽기도 안전함
객체의 모든 필드가 final일 경우, 이는 항상 안전한 게시물을 보장합니까?
답변: 아닙니다, 새 객체에 대한 참조가 생성자가 끝나기 전에 게시되어야만 그렇습니다. 생성자 완료 전에 다른 스레드로 참조가 넘어가면 — 필드가 완전히 초기화되지 않을 수 있습니다. 생성자가 실행되는 동안 this 또는 완전히 구성되지 않은 객체에 대한 참조의 게시를 피해야 합니다.
예제 (위험!):
public class PublishEscape { public static PublishEscape instance; public PublishEscape() { instance = this; // 나쁜! this가 생성자 끝나기 전 게시됨 } }
이야기
개발자는 생성자가 끝나기 전에 static으로 접근 가능한 필드에 Service 인스턴스에 대한 참조를 할당했습니다. 그 결과 다른 스레드는 부분적으로 구성된 객체를 얻어 예측할 수 없는 동작을 일으켰습니다.
이야기
웹 애플리케이션에서 싱글턴 객체가 추가적인 동기화 없이 생성되었습니다. 부하가 걸리면 일부 스레드는 null 또는 잘못 초기화된 필드를 받아 Intermittent NullPointerException을 초래했습니다.
이야기
라이브러리에서는 스레드 간 캐시를 위해 일반 List를 사용했습니다 — 안전한 게시물이 없었고, 초기화가 새로운 스레드에 대한 가시성을 보장하지 않았습니다. 결국 캐시는 혼란스럽게 작동하여 데이터 무결성을 해쳤습니다.