들어가며
동기화는 배타적 실행과 스레드간의 안정적 통신에 사용한다. 다음 코드를 보자.
1 | public class item78 { |
이 코드는 1초후 종료될 것 같지만 끝나지 않는다. JVM이 호이스팅이라는 최적화 기법을 사용하기 때문이다. 즉 while문을 다음과 같이 바꾼다.
1 | if(!stopRequested) |
이는 stopRequested 필드의 읽기와 쓰기를 모두 동기화해 접근하면 문제를 해결할 수 있다. 필드를 volatile로 선언해도 동작할 수 있다. 다음은 volatile을 잘못 사용한 예이다.
1 | private static volatile int nextSerialNumber = 0; |
이유는 여기서 확인할 수 있다. 즉, 할당되어 있는 메모리에 직접 접근했더라도 연산 자체가 원자성을 띄지 않기 때문이다.
동기화가 필요할 땐 java.util.concurrent
패키지를 사용할 수 있다. 락 없이도 thread-safe를 지원하는 클래스들이 존재한다. 내부적으로 락에 대한 연산을 정의해 놨기 때문에 클라이언트 코드에서는 락을 사용하지 않아도 깔끔한 코드를 작성할 수 있다.
이런 문제들을 피하는 가장 좋은 방법은 애초에 가변 데이터를 공유하지 않는 것이다. 불변 데이터만 공유하거나 아무것도 공유하지 말자. 가변 데이터는 단일 스레드에서만 쓰도록 하자. 한 스레드가 데이터를 다 수정한 후 다른 스레드에 공유할 때는 해당 객체에서 공유하는 부분만 동기화 해도 된다. 그러면 그 객체를 다시 수정할 일이 생기기 전까지 다른 스레드들은 동기화 없이 자유롭게 값을 읽어갈 수 있다. 이런 객체를 사실상 불변이라 하고 다른 스레드에 이런 객체를 건네는 행위를 안전 발행이라 한다.