2018년 11월 5일 월요일

[java][thread] volatile 대체 어디서 쓸만한 걸까?

참고 사이트 (내 깃허브): https://github.com/ssisksl77/java-demo/tree/master/src/main/java/concurrent/volatile_test

volatile은 멀티스레드에서 값들을 캐시하지않고 변경된 내용을 바로바로 가져올 수 있게 해준다.
오! 그렇다면 아무 곳에나 volatile만 넣어준다면 마법처럼 동기화가 되면서 공유상태를 사용할 수 있는 것일까?
아래 소스코드와 결과를 보면 그렇제 않은 것 같다.
package volatile_test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VolatileTest1 {
 private volatile static int counter = 0;
 
 public static void main(String[] args) {
  Runnable r1 = () -> { System.out.println(counter); counter++;};
  
  ExecutorService es = Executors.newCachedThreadPool();
  
  for(int i = 0; i < 10; i++) {
   es.execute(r1);
  }
  
  es.shutdown();
//              OUTPUT
//  0
//  0
//  0
//  3
//  4
//  5
//  6
//  7
//  8
//  9

 }
}
0이 3번이나 나왔다. 왜그럴까? 특이한 것은 결과적으로 +1이 잘 되었지만 조회가 잘 안되었다는 것인데 다시 한 번 해보자.
0
0
0
0
0
0
6
6
8
8
전혀 다른 값이 나왔다. 그 말은 값이 달라졌다는 것이다. volatile을 사용한다고 동기화가 되는 것이 아니다. 읽기-수정-쓰기 가 동기화가 되야 하는 것이다. 그렇다면 volatile은 언제 써야 할까? 아래와 같은 경우를 보자.
package volatile_test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VolatileTest2 {
 // 아래 내용을 보자. 과연 잘 돌아가는 경우가 얼마나 있을까?
 private static boolean flag = true;
 
 public static void main(String[] args) {
  Runnable r1 = () -> { 
   while(flag) {
     // 여기에 막히게 된다. 
   }
   System.out.println("bye");
  };
  
  Runnable r2 = () -> { System.out.println("flag false start"); flag = false;};
  ExecutorService es = Executors.newCachedThreadPool();
  
  for(int i = 0; i < 10; i++) {
   es.execute(r1);
  }
  es.execute(r2);
  
  es.shutdown();

 }
}
여기서는 전혀 돌아가지는 않는다. 이것은 volatile의 부재일 수도 있지만, 컴파일러 마다 최적화를 하면서 결과값이 다르게 나올 수도 있다고 한다. 여튼 위 내용은 현재 flag를 false로 변경했지만 먹히지 않는다. 이때 volatile을 넣어보자.
package volatile_test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VolatileTest3 {
 // 이럴때 volatile로 바꾸는 것만으로 효과가 있다.
 private volatile static boolean flag = true;
 
 public static void main(String[] args) {
  Runnable r1 = () -> { 
   while(flag) {
    // System.out.println("hi");
   }
   System.out.println("bye");
  };
  
  Runnable r2 = () -> { System.out.println("flag false start"); flag = false;};
  ExecutorService es = Executors.newCachedThreadPool();
  
  for(int i = 0; i < 10; i++) {
   es.execute(r1);
  }
  es.execute(r2);
  
  es.shutdown();

 }
}

댓글 없음:

댓글 쓰기