public class StopThread {
private static 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;
}
}
위 코드는 1초후에 멈추는 프로그램이라고 예상되어진다.
왜?
static 변수인 stopRequested
가 1초 후에 true가 되었기 때문이다.
static 변수는 쓰레드가 공유하는 변수이다.
따라서 backgroundThread
와 mainThread
가 서로 공유하는 변수이기 때문에
1초후에 멈추는 프로그램일것이다.
하지만 멈추지 않는다.
왜?
동기화가 되지 않았기 때문이다.
내가 알고 있는 동기화는 메소드에 동기화(synchronized)를 걸었다면 어떤 쓰레드가 synchronized 메소드를 읽기전에 해당 메소드를 lock 하여 다른 쓰레드가 접근하지 못하게 하고 메소드를 실행한다. 실행 후에는 lock 을 해제하고 대기중이던 다른 쓰레드가 접근하게 된다.
이런 메커니즘이 동기화와 어떤 관런이 있다는 것일까?
두 쓰레드가 synchronized 나 volatile 키워드를 사용하지 않으면 static 변수를 메인 쓰레드에서 변경했다고 하더라도 다른 쓰레드에 적용될지 안될지는 모른다.
왜냐하면 cpu 가 두개이상이라면 두 cpu 에서 각각의 변수을 메인 메모리가 아니라 캐시 메모리에 복사하여 가져온다. 이때 한쪽의 변수를 수정했다고해서 메인 메모리에 바로 적용되는 것이 아니기 때문에 다른 쓰레드에서 변경된 변수를 사욭할 수가 없고 최초 복사했던 변수를 사용하는 것이다.
처음에 이해했던 방식은 두 쓰레드 스택에서 힙에 있는 공통된 객체를 바라보고 있기 때문에 당연히 변수를 공유했다고 생각했던 것이다.
그런데 두개가 당연히 공유하는 것이아니라 cpu 코어가 2개 이상이라면 쓰레드들은 각각의 cpu에서 작동할테고 한쪽에서 메인메모리에서 cpu 캐시메모리로 가져가서 수정된 변수는 바로 메인메모리에 저장되지 않기 때문에 서로 같은 값을 볼 수 없는 것이다.
cpu 는 cpu가 연산할 때 register 와 캐시 메모리 그리고 메인메모리 사이를 데이터들이 꼭 이동하게 되기때문인데, 이를 메인메모리에 동기화시켜 사용하고 싶으면 volatile 이나 synchronized 키워드를 꼭 사용해야한다.
보면서 공부한 링크들:
https://madplay.github.io/post/synchronize-access-to-shared-mutable-data
https://parkcheolu.tistory.com/14
https://github.com/java-squid/effective-java/issues/80