동기화란?
- 여러 스레드가 한개의 자원을 사용할 때 점유한 스레드를 제외 하고 나머지 스레드들은 접근하지 못하도록 베타적인 락을 통해 보호 기능을 사용하는 것을 말함. (Synchronized)
- 하지만 자바에서는 volatile변수, 명시적 락, 단일 연산 변수(Atomic variable)을 사용하는 경우에도 '동기화'라는 용어를 사용 함.
- 동기화는 일관성이 깨진 상태를 볼 수 없게 함
- 스레가드가 같은 락의 보호하에 수행된 모든 이전 수정의 최종 결과를 보게 해줌
- long과 double 외에 변수를 읽고 쓰는 동작은 원자적이다.
- 그렇다고 해서 성능을 높으려고 읽고 쓸때 동기화를 하지 않으면 아주 위험한 발상
- 필드를 읽을 때 항상 수정이 완전히 반영된 값을 얻는다고 보장
- 한 스레드가 저장한 값이 다른 스레드에게 보이는가는 보장하지 않음
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
Thread backgroukndTrehad = new Thread(() -> {
int i = 0;
while (!stopRequested) {
i++;
}
});
backgroukndTrehad.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
메인 스레드가 1초 후 stopReqeusted를 ture로 설정하면 반복문을 빠져나올 것처럼 보임
하지만 영원히 수행 됨
가상 머신은 다음과 같이 끌어올리기 (hoisting)라는 최적화 기법을 수행함.
//원래 코드
while (!stopRequested)
i++;
// 최적화한 코드
if (!stopRequested)
while(true)
i++;
stopRequested필드를 동기화 해 접근하면 이 문제를 해결할 수 있다.
쓰기 메서드와 읽기 메서드 모두 동기화 했음을 주목
쓰기와 읽기가 동기화되지 않으면 동작을 보장하지 않음
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 backgroukndTrehad = new Thread(() -> {
int i = 0;
while (!stopRequested()) {
i++;
}
});
backgroukndTrehad.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
volatile과 안전 실패(safety failure)
- 위 코드에서 stopRequeted 를 volatile으로 선언하면 동기화를 생략해도 됨
- volatile은 배타적 수행과 상관 없지만 항상 가장 최근에 기록된 값을 일게 됨을 보장
- volatile은 주의해서 사용해야 함.
private static voliatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
- 일련번호를 생성할 의도로 작성한 코드이고 매번 고유한 값을 반환할 의도로 만들어짐
- 동기화 하지 않더라고 불변식을 보호할 수 있어 보이지만 동기화 없이 올바르게 동작하지 않음
- 문제는 증가 연산자
- 실제로 nextSerialNumber 필드에 두번 접근
- 먼저 값을 읽고 그런 다음 증가한 새로운 값을 저장
- 만약 두 번째 스레드가 이 두 접근 사이를 비집고 들어와 값을 읽어가면 첫 번째 스레드와 똑같은 값을 받게됨
- 이런 오류를 안전 실패라고 함
- generateSerialNumber메소드에 synchronized를 붙이면 해결
AtuomicLong
volatile은 동기화의 두 효과 중 통신 쪽만 지원하지만 이 패키지는 원자성(배타적 실행)가지 지원
private static final AtomicLong nextSerialNum = new AtomicLong();
public static long generateSerialNumber() {
return nextSerialNum.getAndIncrement();
}
문제들을 피하는 방법
애초에 가변 데이터를 공유하지 않거나 불변 데이터만 공유하거나 아무것도 공유하지 말자
가변 데이터는 단일 스레드에서만 쓰도록 하자
여러 스레드가 가변 데이터를 공유한다면 그 데이터를 읽고 쓰는 동작은 반드시 동기화 해야함.
'책정리 > EfectiveJava' 카테고리의 다른 글
지연 초기화는 신중히 사용하라 (0) | 2019.04.22 |
---|---|
스레드 안전성 수준을 문서화 하라 (0) | 2019.04.22 |
wait와 notify보다는 동시성 유틸리티를 이용하라 (0) | 2019.04.21 |
스레드보다는 실행자, 태스크, 스트림을 애용하라 (0) | 2019.04.21 |
과도한 동기화는 피하라 (0) | 2019.04.17 |