본문 바로가기

JAVA

Java 멀티쓰레드 동기화 - (1) Volatile

728x90

Java에는 멀티쓰레드 환경에서 동기화를 하기 위한 3가지 방법이 있다.

그중 Volatile 키워드에 대해서 설명하겠습니다.

1. volatile 키워드

 

JAVA에서 volatile 키워드는  변수를 read 하고 write 할때 CPU cache가 아닌 메인 메모리에다가 한다고 명시적으로 선언하는 것이다. 보통의 메모리 구조는 아래와 같이 생겼다.

 

CPU 내에는 성능 향상을 위해서 L1 cache가 내장 되어있습니다. 쓰레드에서 같은 변수를 참조하려고 해도 쓰레드마다 다른 캐시를 참조하기 떄문에 불일치 문제가 생깁니다. 이해하기 쉽게 한쪽 쓰레드에서는 flag 를 검사하는 쓰레드(쓰레드 1), 한쪽 쓰레드에서는 flag의 값을 바꾸는  (쓰레드2) 2개의 쓰레드를 만들었습니다.

 

public class ThreadTest {
	boolean running = true;

	public void test() {
		new Thread(()->{
				int counter = 0;
				while (running) {
					counter++;
				}
				System.out.println("Thread 1 finished. Counted up to " + counter);
			}
		).start();
		new Thread(()-> {
				// Sleep for a bit so that thread 1 has a chance to start
				try {
					Thread.sleep(100);
				} catch (InterruptedException ignored) {
					// catch block
				}
				System.out.println("Thread 2 finishing");
				running = false;
			}
		).start();
	}

	public static void main(String[] args) {
		new ThreadTest().test();
	}
}

쓰레드 2가 실행되면 running이 false로 바뀌어 쓰레드 1의 무한루프가 종료될것이라고 예상되지만 쓰레드 1은 영원히 종료되지 않습니다. 그 이유는 쓰레드 1이 running 변수을 참조할 때 자신의 CPU cache를 참조 하기 때문이고 쓰레드 2는 자신의 CPU cache 의 running 에만 썼기 때문에 같은 변수임에도 서로 다른 메모리 주소를 참조하게 되는 것입니다.

 

이와 같은 동기화 현상을 방지하는 것이 volatile 키워드입니다. 변수를 volatile로 선언하면 메인 메모리 영역을 참조하게 되므로 다른 스레드라도 같은 메모리 주소를 참조 할 것입니다. 위의 예제에서 쓰레드 2가 running 을 false로 write하면 쓰레드 1이 running 을 참조할때 메인 메모리를 참조하므로 running이 false로 되어 무한루프에서 탈출할 수 있습니다.

 

2. Volatile의 문제점

멀티쓰레드 동기화에는 크게 두가지 문제가 있습니다.

첫번째는 race condition (여러 개의 쓰레드가 동시에 경쟁하는 경우)

두번쨰는 Visibility (메모리 가시성)

 

Volatile은 두번째 문제를 해결합니다. 그러나 Volatile은 race condition은 해결하지 못합니다.

 

예를 들어 서로 다른 두 스레드에서 변수 a=0 에 대해 a++를 한다고 생각하면 

하나의 쓰레드에선 a= 1 다른 하나에선 a=2 가 될 것이라고 예상하지만, 이 결과가 반드시 보장되지는 않습니다.

 

a++ 는 사실 하나의 instruction가 아니라 read a-> a= a+1 -> save a  라는 3개의 명령어로 나눌 수 있습니다.

CPU가 명령을 같은 순서로 실행되면 두 쓰레드에서 모두 a=1이라는 결과가 나올것입니다.

T1 T2
read a from main memory(a=0)  
  read a from main memory(a=0)
a= a+1  
  a = a +1
save(a) => 1 to main memory   
  save(a) => 1 to main memory

 

이렇게 Volatile은 여러 쓰레드의 Race Condiiton을 해결하지 못하기 때문에 2개 이상의 쓰레드가 Write 하려는 경우에는 부적합합니다. 반면 여러개의 쓰레드가 Read하고, 하나의 쓰레드가 Write하려는 상황에는 최적의 해결안입니다. volatile은 다른 동기화 방법 처럼 lock 을 사용하지 않기 떄문에 비용이 적다는 장점이 있기 때문입니다.

 

 

출처 : nesoy.github.io/articles/2018-06/Java-volatile

 

Java volatile이란?

 

nesoy.github.io

 

 

'JAVA' 카테고리의 다른 글

Java 멀티쓰레드 동기화 - (3) AtomicClass  (0) 2020.10.24
Java 멀티쓰레드 동기화 - (2) Synchronized  (0) 2020.10.11
JPA 프록시와 연관관계 관리  (0) 2020.09.04
Spring Data JPA  (0) 2020.08.30
JDBC, MyBatis  (0) 2020.08.27