아이템 66. 변경 가능 공유 데이터에 대한 접근은 동기화하라
66. 변경 가능 공유 데이터에 대한 접근은 동기화하라
상호 배제성뿐 아니라 스레드 간의 안정적 통신을 위해서도 동기화는 반드시 필요하다.
읽기 연산과 쓰기 연산에 전부 적용하지 않으면 동기화는 아무런 효과도 없다.
동기화를 구현하는 방법
synchronized
// 적절히 동기화한 스레드 종료 예제
public class StopThread {
private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while(!stopRequested())
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
volatile
// 적절히 동기화한 스레드 종료 예제
public class StopThread {
private static volatile boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while(!stopRequested)
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
volatile
은 상호배제를 구현하지 않지만 가장 최근에 기록된 값을 조회하도록 보장한다.
잘못된 volatile
사용
// 잘못된 예제 - 동기화가 필요하다!
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
문제는 ++
연산자가 원자적이지 않다는데 있다. 이 연산자는 두가지 연산을 순서대로 시행한다.
- 증가할 값을 읽는다.
- 새로운 값(읽은 값에 +1을 더해서)을 필드에 쓴다.
첫 번째 쓰레드가 필드의 값을 읽은 후 새 값을 미처 기록하기 전에 두 번째 스레드가 필드에서 같은 값을 읽으면, 두 스레드는 같은 일련번호를 얻게 될 가능성이 있다.
위와 같은 문제를 해결 하는 방법
volatile
키워드를 제거하고synchronized
키워드 사용하고 더욱 견고하게 구현하려면int
대신long
사용-
Atomic
클래스 사용, 이 클래스는java.util.concurrent.atomic
패키지에 포함되어 있으며,synchronized
키워드를 쓰는 것 보다 성능이 좋음private static final AtomicLong nextSerialNum = new AtomicLong(); public static long generateSerialNumber() { return nextSerialNum.getAndIncrement(); }
동기화 솔루션의 역발상
변경 가능 데이터를 공유하지 않는 것 - 동시성 이슈가 발생하지 않음
- 변경 불가능 데이터(불변객체)를 공유
- 변경 가능한 데이터는 한 스레드만 이용하도록 한다 (공유하지 않음)
실질적으로 변경 불가능한 객체(effectively immutable)
특정한 스레드만이 데이터 객체를 변경할 수 있도록 하고, 변경이 끝난 뒤에 다른 스레드와 공유하도록 할 때는 객체 참조를 공유하는 부분에만 동기화를 적용함 객체 참조를 가져온 스레드는, 객체가 더 이상 수정되지 안흔 한 동기화 없이도 객체의 내용을 읽을 수 있음, 이런 객체를 실질적으로 변경 불가능한 객체(effectively immutable) 라고 함
결론
변경 가능한 데이터를 공유할 때는 해당 데이터를 읽거나 쓰는 모든 스레드는 동기화를 수행해야 한다.