요약
•
멀티 스레딩 환경에서 가변 데이터를 사용하려면 동기화가 꼭 필요하다.
•
가변 데이터는 가급적 단일 스레드에서만 사용하자.
스레드 동기화
•
언어 명세상 long과 double을 제외한 변수를 읽고 쓰는 동작은 원자적(atomic)이다. 하지만 이를 믿고 동기화를 하지 않는다면, 스레드가 필드를 읽을 때 항상 수정이 반영된 값을 얻는 것은 보장하지만 다른 스레드가 해당 값을 제대로 읽어 간다고 보장하지 못한다.
public Class stopThread {
private static int testValue = 0;
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) testValue++;
}).start();
new Thread(() -> {
for (int j = 0; j < 5; j++) testValue--;
}).start();
}
Thread.sleep(1000);
System.out.println(testValue);
}
}
Java
복사
•
실제 위 코드를 여러 번 동작시킨 결과 5000, 4991, 5002, 4990, 5000, 4998의 값이 출력되었다. 이렇게 프로그램이 잘못된 결과를 계산해내는 것을 안전 실패(safety failure)라고 한다.
public Class stopThread {
private static volatile int testValue = 0;
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) testValue++;
}).start();
new Thread(() -> {
for (int j = 0; j < 5; j++) testValue--;
}).start();
}
Thread.sleep(1000);
System.out.println(testValue);
}
}
Java
복사
•
이와 같이 volatile 키워드를 적용하더라도 5000, 5000, 5010, 5000, 5000, 4990 값이 출력되었다.
•
volatile은 CPU 캐싱 없이 메모리에서 데이터를 읽어오기만 할 뿐 동기화 작업을 해주지는 않는다. 위 코드에서 문제는 testValue++과 같이 데이터를 읽고 더하는 2번의 명령이 수행되는 사이에 다른 스레드가 비집고 들어와 값을 바꾸는 것이 문제이다.
public Class stopThread {
private static int testValue = 0;
private synchronized static void increase() {
testValue++;
}
private synchronized static void decrease() {
testValue--;
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) increase();
}).start();
new Thread(() -> {
for (int j = 0; j < 5; j++) decrease();
}).start();
}
Thread.sleep(1000);
System.out.println(testValue);
}
}
Java
복사
•
하지만 이와 같이 synchronized 키워드를 통해 읽기와 쓰기 모두 동기화 후에는 항상 5000의 값만 출력된다.
public Class stopThread {
private static AtomicInteger testValue = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) testValue.incrementAndGet();
}).start();
new Thread(() -> {
for (int j = 0; j < 5; j++) testValue.decrementAndGet();
}).start();
}
Thread.sleep(1000);
System.out.println(testValue.get());
}
}
Java
복사
•
혹은 이와 같이 java.tuil.concurrent.atomic 패키지의 AtomicInteger를 사용하면, 별도의 동기화 없이도 스레드 안전한 프로그램을 작성할 수 있다.
가변 데이터와 스레드
•
가변 데이터를 멀티 스레딩 환경에서 사용하면 위와 같은 문제들이 발생한다.
•
그러니 가장 좋은 방법은 가변 데이터를 공유하지 않는 것이다. 멀티 스레딩 환경에서는 불변 데이터만 공유하거나 아무것도 공유하지말고, 가변 데이터는 단일 스레드에서만 사용하자.
•
하나의 스레드가 데이터를 다 수정한 후 다른 스레드에 공유하는 부분만 동기화 해도 사실상 불변(effectively immutable)로, 다른 스레드에 이런 객체를 건네는 행위를 안전 발생(safe publication)이라 한다.