자바 메모리 모델은 특정 스레드에서 메모리를 대상으로 취하는 작업이 다른 스레드에게 어떻게 보이는지의 여부를 명시하고 있다.
메모리 모델 : 프로그램이 메모리 구조에서 어느 정도의 기능을 사용할 수 있을지에 대한 정보를 제공하고, 메모리의 내용을 서로 공유하고자 할 때 프로세스 간의 작업을 조율하기 위한 특별한 명령어(memory barrier)로는 어떤 것들이 있으며 어떻게 사용해야 하는지에 대한 정보를 제공한다.
JMM은 서로 다른 하드웨어가 갖고 있는 각자의 메모리 모델을 직접 신경쓰지 않도록 한다.
재배치 reordering : 특정 작업이 지연되거나 다른 순서로 실행되는 것처럼 보이는 문제
미리 발생(happens-before) 현상 규칙
volatile
변수 규칙 ; volatile
변수에 대한 쓰기 작업은 이후에 따라오는 해당 변수에 대한 모든 읽기 작업보다 미리 발생한다.Thread.start
작업은 시작된 스레드가 갖고 있는 모든 작업보다 미리 발생한다interrupt
메서드를 호출하는 작업은 인터럽트 당한 스레드에서 인터럽트를 당했다는 사실을 파악하는 일보다 미리 발생한다.finalizer
) 규칙 : 특정 객체에 대한 생성 메소드가 완료되는 시점은 완료 메서드가 시작하는 시점보다 미리 발생한다.transitivity
) : A가 B보다 미리 발생하고, B가 C보다 미리 발생한다면, A는 C보다 미리 발생한다.JMM은 강력한 미리 발생 규칙에 따라 동작함에도, 안전한 공개 기법을 사용하는 이유
프로그램을 작성할 때는 개별적으로 메모리에 쓰기 작업이 일어난 이후의 가시성을 놓고 안전성을 확인하기보다 객체의 소유권을 넘겨주고 공개하는 작업이 훨씬 안전하기 때문 lazy initialization 을 잘못 사용하면 스레드 안전하지 않을 수 있다.
스레드 안전한 초기화 방법
`@ThreadSafe public class SafeLazyInitialization { private static Resource resource;
public synchronized static Resource getInstance() {
if (resource == null) {
resource = new Resource();
}
return resource;
}
}`
늦은 초기화 홀더 클래스
`@ThreadSafe public class ResourceFactory { private static class ResourceHolder { public static Resource resource = new Resource(); }
public static Resource getResource() {
return ResourceHolder.resource;
}
}`
초기화 안전성을 보장한다는 의미는 올바르게 생성된 불변 객체를 어떤 방법으로건 (데이터 경쟁이 발생하는 방법이더라도), 여러 스레드에서 별다른 동기화 구문 없이 안전하게 사용할 수 있다는 의미이다.
final
로 선언된 변수를 갖고 있는 클래스는 초기화 안전성 조건 때문에 해당 인스턴스에 대한 참조를 최초로 생성하는 과정에서 재배치 작업이 일어나지 않는다.final
변수를 통해 접근 가능한 모든 변수에 값을 쓰는 작업은 생성 메소드가 종료되는 시점에 ‘고정’된다.final
로 선언되지 않은 변수나 생성 메서드가 종료된 이후에 변경되는 값에 대해서는 별도의 동기화 구문을 적용해야 가시성을 확보할 수 있다.