본문 바로가기

JAVA

Volatile 을 사용한 가시성 보장

쓰레드(3) - Synchronized

 

쓰레드(3) - Synchronized

1. 쓰레드와 밀접한 연관이 있는 Synchronized synchronized는 자바의 예약어 중 하나이다. 즉, 변수명, 클래스명으로 사용이 불가능하다. StringBuffer와 StringBuilder의 차이점으로 StringBuffer는 "쓰레드에..

dev-cool.tistory.com

 

멀티 쓰레드 환경에서 원자성을 보장하기 위해서 우리는 synchronized 예약어를 사용하여 쓰레드의 동시접근을 제한하였다.

또다른 동기화 방법으로 volatile 예약어를 사용할 수 있다.

 

1. Volatile


 

http://tutorials.jenkov.com/images/java-concurrency/java-volatile-1.png

 

volatile 예약어를 설명하기 위해서 메모리 구조를 나타내는 그림을 첨부하였다.

CPU 내부에는 성능향상을 위해서 cache가 내장되어 있는데,
쓰레드에서 같은 값을 참조하려 해도 쓰레드 마다 다른 캐시를 참조하기 때문에 변수가 일치하지 않는 현상이 나타난다.

이때 volatile 예약어를 사용하는 것은 CPU 캐시에서 변수를 Read, Write 하는것이 아니라 Main Memory 에서 Read, Write 를 하겠다는걸 의미한다.

즉,

  • volatile 예약어는 'Main Memory 에 변수를 저장하겠다' 라는 의미
  • 변수의 값을 Read 할때는 CPU cache 에서 읽어오는 것이 아닌 'Main Memory 에서 읽어옴'
  • 변수의 값을 Write 할때는 'Main Memory 의 변수에 작성하겠다' 라는것을 의미

 

이번엔 코드를 작성해 보면서 volatile 예약어를 사용해보자.

public class VolatileThread {
    //volatile
    boolean runFlag = true;

    public void test() {
        new Thread(()->{
            int count = 0;
            while (runFlag) {
                count++;
            }
            System.out.println("Thread 1 finished. count : " + count);
        }
        ).start();
        new Thread(()-> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            System.out.println("Thread 2 finishing");
            runFlag = false;
        }
        ).start();
    }

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

 

간단하게 Test 코드를 작성하여 실행하게 되면

총 2개의 쓰레드를 실행하였는데,
한개의 쓰레드는 runFlag 가 true 이면 반복문을 계속해서 돌게되고
다른 쓰레드는 runFlag 를 false로 바꾸는 연산을 수행한다.

하지만 위의 코드를 실행해보면 쓰레드는 반복문을 빠져나가지 못하고, 두번째 쓰레드의 출력문만 출력되게 된다.

그 이유는 첫번째 쓰레드에 있는 runFlag 변수와 두번째 쓰레드에 있는 runFlag 변수가 서로의 cache 에서 다르게 저장되고 있기 때문이다.

따라서 runFlag 변수 앞에 volatile 예약어를 사용한다면, 두 쓰레드는 같은 변수를 사용하고 있으므로 첫번째 쓰레드는 정상정으로 종료될 것이다.

 

2. Volatile 의 문제점


volatile 을 사용하면 thread-safe 하다고 할 수 있는가?


결론부터 말하자면 thread-safe 하다고 볼 수 없다.

그 이유는 멀티 쓰레드 환경에서
Read 작업이 아닌 Write 작업을 수행하는 경우 일부 쓰레드의 경우 연산이 정상적으로 수행되지 않을 수 있기 때문이다.

이러한 경우에는 synchronized 예약어를 사용해서 원자성을 보정해야한다.