78. 공유 중인 가변 데이터는 동기화해 사용하라

syncronized 키워드는 해당 메서드나 블록을 한번에 한 스레드씩 수행하도록 보장한다.

많은 프로그래머가 동기화를 배타적 실행, 즉 한 스레드가 변경하는 중이라서 상태가 일관되지 않은 순간의객체를 다른 스레드가 보지 못하게 막는 용도로만 생각한다.

먼저 이 관점에서 얘기해보자. 한 객체가 일관된 상태를 가지고 생성되고, 이 객체에 접근하는 메서드는 그 객체에 락을 건다.

락을 건 메서드는 객체의 상태를 확인하고 필요하면 수정한다.

즉, 객체를 하나의 일관된 상태에서 다른 일관된 상태로 변화시킨다.

동기화를 제대로 사용하면 어떤 메서드도 이 객체의 상태가 일관되지 않은 순간을 볼 수 없을 것이다.

맞는 설명이지만, 동기화에는 중요한 기능이 하나 더 있다.

동기화 없이는 한 스레드가 만든 변화를 다른 스레드에서 확인하지 못할 수 있다.

동기화는 일관성이 깨진 상태를 볼 수 없게 하는 것은 물론, 동기화된 메서드나 블록에 들어간 스레드가 같은 락의 보호하에 수행된 모든 이전 수정의 최종 결과를 보게 해준다.

언어 명세상 long과 double 외의 변수를 읽고 쓰는 동작은 원자적이다.

여러 스레드가 같은 변수를 동기화 없이 수정하는 중이라도, 항상 어떤 스레드가 정상적으로 저장한 값을 온전히 읽어옴을 보장한다는 뜻이다.

이 말을 듣고 “성능을 높이려면 원자적 데이터를 읽고 쓸 때는 동기화하지 말아야겠다”고 생각하기 쉬운데, 아주 위험한 발상이다.

동기화는 배타적 실행뿐 아니라 스레드 사이의 안정적인 통신에 꼭 필요하다.

이는 한 스레드가 만든 변화가 다른 스레드에게 언제 어떻게 보이는지를 규정한 자바의 메모리 모델 때문이다.

반복문에서 매번 동기화하는 비용이 크진 않지만 속도가 더 빠른 대안이 있다.

volatile 로 선언하면 동기화를 생략해도 된다.

volatile 한정자는 배타적 수행과는 상관없지만 항상 가장 최근에 기록된 값을 읽게 됨을 보장한다.

volatile은 주의해서 사용해야 한다. 예를 들어 다음은 일련번호를 생성할 의도로 작성한 메서드다

private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {
	return nextSerialNumber++;
}