synchronized
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(() -> {
int i = 0;
while (!stopRequested())
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
쓰기와 읽기 모두 동기화해야 동작을 보장할 수 있습니다. 이때, volatile 한정자를 사용해 동기화에 대한 비용을 줄일 수 있습니다.
volatile 필드 사용
public class StopThread {
private static volatile boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(() -> {
int i = 0;
while (!stopRequested)
i++;
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
volatile 사용시 주의사항
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
증가 연산자(++)사용시 nextSerialNumber필드에 두 번 접근(값을 읽고, 1증가한 값을 저장)합니다. 이때, 두 번째 스레드가 두 접근 사이를 비집고 들어와 값을 읽어가면 첫 번쨰 스레드와 똑같은 값을 돌려받습니다. 이런 경우 generateSerialNumber 메서드에 synchronized 한정자를 붙이고 volatile을 제거합니다.
java.util.concurrent.atomic을 이용한 락-프리 동기화
private static final AtomicLong nextSerialNumber = new AtomicLong();
public static long generateSerialNumber() {
return nextSerialNumber.getAndIncrement();
}
java.util.concurrent.atomic을 사용하면 동기화 버전보다 성능이 좋으며 원자성까지 지원합니다.
반응형