티스토리 뷰
동기화란?
- 해당 메서드나 블록을 한번에 한 스레드씩 수행하도록 보장
동기화의 기능
- 동기화는 일관성이 깨진 상태를 볼 수 없게 하는 것은 물론, 동기화된 메서드나 블록에 들어간 스레드가 같은 락의 보호하에 수행된 모든 이전 수정의 최종 결과를 보게 해준다
- 동기화가 없다면 한 스레드가 저장한 값이 다른 스레드에게 언제 '보일지' 알 수 없다. 동기화는 배타적 실행 뿐 아니라 스레드 사이의 안정적인 통신에 꼭 필요하다.
잘못된 코드
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while(!stopRequested)
i++;
});
backgroundThread.start();
TimUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
- 동기화 작업이 되어있지 않아 메인스레드가 수정한 값을 백그라운드 스레드가 언제쯤 보게 될지 알수 없다
동기화 적용된 코드
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) {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while(!stopRequested())
i++;
});
backgroundThread.start();
TimUnit.SECONDS.sleep(1);
requestStop();
}
}
- 쓰기와 읽기 모두가 동기화되지 않으면 동작을 보장하지 않는다
volatile
- volatile 한정자는 배타적 수행과는 상관없지만 항상 가장 최근에 기록된 값을 읽게 됨을 보장한다.
public class StopThread {
private static volatile boolean stopRequested;
public static void main(String[] args) {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while(!stopRequested)
i++;
});
backgroundThread.start();
TimUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
- volatile는 주의해서 사용해야 한다
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSeriaINumber++;
}
- 문제는 중가 연산자(++)다. 이 연산자는 코드상으로는 하나지만 실제로는 nextSerialNumber 필드에 두 번 접근한다.
- 먼저 값을 읽고, 그런 다음 (1 증가 한) 새로운 값을 저장하는 것이다. 만약 두 번째 스레드가 이 두 접근 사이를 비집고 들어와 값을 읽어가면 첫 번째 스테드와 똑같은 값을 돌려받게 된다.
- 프로그램이 잘못된 결과를 계산해내는 이런 오류를 안전 실패(safety failure)라고한다.
- generateSerialNumber메소드에synchronized한정자를 붙이고 필드에 volatile를 제거하면 해결된다
- 더 우수한 해결책은 성능도 우수하고 락없이 스레드 안전한 클래스 사용
private static final AtomicLong nextSerialNum = new AtomicLong();
public static long generateSerialNumber() {
return nextSerialNum.getAndlncrement();
}
문제를 피하는 가장 좋은 방법
- 가장 좋은 방법은 애초에 가변 데이터를 공유하지 않는 것이다. 불변 데이터판 공유하거나 아무것도 공유하지 말자.
- 가변 데이터는 단일 스레드에서만 쓰도록 하다 혹은 한 스레드가 데이터를 다 수정한 후 다른 스레드에 공유 할 때는 해당 객체에서 공유하는 부분만 동기화 해도 된다. 이런 객체를 사실상 불변(effectively immutable)이라 하고 다른 스레드에 이런 객체를 건네는 행위를 안전 발행(safe publication)이라 한다.
핵심 정리
여러 스레드가 가변 데이터를 공유한다면 그 데이터를 읽고 쓰는 동작은 반드시 동기화 해야 한다. 동기화하지 않으면 한 스레드가 수행한 변경을 다른 스레드가 보지 못할 수 도 있다, 공유되는 가변 데이터를 동기화하는 데 실패하면 응답 불가 상태에 빠지거나 안전 실패로 이어질 수 있다. 이는 디버깅 난이도가 가장 높은 문제에 속한다. 간헐적이거 나 특정 타이밍에만 발생할 수도 있고, VM에 따라 현상이 달라지기도 한다. 배타적 실행 은 필요 없고 스레드끼리의 통신만 필요하다면 volatile 한정자만으로 동기화할 수 있 다. 다만 올바로 사용하기가 까다롭다.
'Java' 카테고리의 다른 글
[Effective Java] 스레드보다는 실행자, 테스크, 스트림을 애용하라 (0) | 2021.07.26 |
---|---|
[Effective Java] 과도한 동기화는 피하라 (0) | 2021.07.25 |
[Effective Java] 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 (0) | 2021.07.23 |
[Effective Java] 인스턴스 수를 통제해야 한다면 readResolve보다는 열거타입을 사용하라 (0) | 2021.07.20 |
[Effective Java] readObject 메서드는 방어적으로 작성하라 (0) | 2021.07.18 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Today
- Yesterday
링크
TAG
- exception
- jvm
- Design Pattern
- 영속성
- reactive
- Effective JAVA
- Observer Pattern
- 디자인패턴
- observable
- template method
- JMeter
- strategy
- reactive stream
- concurrency
- Java
- nosql
- 부하테스트
- 메인보드#asrock b650m #조립pc #후기이벤트
- iterable
- Serialize
- in-memory
- Spring
- Serializable
- object
- Redis
- gslb
- LAMBDA
- template
- Concurrecy
- jdk11
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
글 보관함