2014년 2월 15일 토요일

자바쓰레드(JAVA Thread) Volatile, transient, java.util.concurrent.atomic.Atomic*,JAVA쓰레드,멀티쓰레드,자바공부학습,자바기초교육,자바실무교육

자바쓰레드(JAVA Thread) Volatile, transient, java.util.concurrent.atomic.Atomic*,JAVA쓰레드,멀티쓰레드,자바공부학습,자바기초교육,자바실무교육 
 
volatile은 컴파일러에게 이 데이터( 변수, 메소드 )에 대해 멀티쓰레드로 접근하고 있음을 알려주는 것인데  읽기 쓰기에 대해서 동기화를 보장하기때문에 long 타입등에 thread safety 하다.

즉 하나의 변수를 여러 쓰레드에서 사용할 때 사용하는 키워드이다. 멀티 쓰레드가 어떤 변수에 접근하는 경우 그 값이 예측불허하게 바뀌는것을 막기 위한 기능이다.

synchronized는 행위에 대한 동기화이며 volatile은 행위의 타겟에 대한 동기화 이다.
하나의 변수를 멀티 쓰레드에서 사용하게 되면 각 쓰레드마다 해당 변수 값을 저장하는 작업 복사본을 하나씩 가지고 있어서 쓰레드는 원본에서 값을 접근하기 위해 자신의 작업 복사본에 값을 저장하고 어떠한 연산을 거친뒤에 다시 원본 값을 돌려버리는 방식을 취하는데 이럴경우 동기화에 문제가 있다.

만약 화면에 변수 값을 찍는 작업을 위해 하나의 쓰레드가 자신의 작업 복사본에 값을 읽어 들이고 그 값을 찍기 이전에 다른 쓰레드가 그것을 변경했다고 한다면 한 쪽 쓰레드는 변경되지 이전의 값이 존재하고 다른 한 쪽은 변경된 값을 가지게 된다.

이것을 막기 위해 volatile을 사용하면 쓰레드가 특정 변수를 접근하려는 연산이 발생하면 무조건 다시 주 원본의 값을 가져오게 하는 것이다.

일반적으로 어떤 리소스가 있을때 동시에 여러 쓰레드가 접근한다면 안정적이지 않을 수 있다.
하지만 int형은 예외가 되는 것이 대입 연산 자체가 4바이트 원자 연산(32비트 컴퓨팅 환경에서)이며, int형이 쪼개질수 없다는 것이 바로 우연하게도 스레드에 대해 안정적이 되는 이유이다. 하지만 이것은 대입 연산 자체가 원자연산이라서 그런것이라기보다 int형이 32bit이고 컴퓨터가 32bit라서 그렇다.

반대로 자바에서 double형과 float형도 쪼개질 우려가 있다. double형이 64bit라면 이것이 CPU로 두번씩 나누어 전송되어야 하는데 이것이 문제가 될 소지가 있음로 volatile을 붙이게 되는 것이다.
하지만 volatile은 읽기 쓰기에 대한 동기화를 보장하고 연산에 대해서는 동기화를 보장하지 않으므로 java.util.concurrent.atomic.Atomic* 클래스들을 사용한다.
 
transient는 객체 직렬화시에 멤버 변수의 전송을 막기 위한 것이다.
 
Java SE 5 이후로, java.concurrent.Atomic.* 클래스가 따로 제공되어 원자성을 제공한다.
아래 예제를 보면 volitile변수의 값도 동기화가 되지 않는데 이는  변수가 다른 스레드에 의해 데이터가 변경되었는지를 알 수 있지만 그것에 대한 제어(lock)는 제공하지 않기 떄문이다.

아래 예제를 보자.
 
package onj;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicTest {
  static AtomicInteger i = new AtomicInteger(0);
 //volatile int i = 0;
 public static void main(String args[]) throws InterruptedException {
  AtomicTest t = new AtomicTest();
  Onj t1 = new Onj("A", t);
  Onj t2 = new Onj("B", t);
  Onj t3 = new Onj("C", t);
  Onj t4 = new Onj("D", t);
  Onj t5 = new Onj("E", t);
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
  Thread.sleep(2000);
  System.out.println(t.i);
 }
}
class Onj extends Thread {
 String name;
 AtomicTest t;
 public Onj(String str, AtomicTest t) {
  this.name = str;
  this.t = t;
 }
 public void run() {
  for (int i = 0; i < 10000; i++) {
   AtomicTest.i.getAndAdd(1);
   //t.i = t.i + 1;
  }
 
  System.out.println(name + "::::: END");
 }
}

[결과]
1. AtomicInteger를 사용한 경우
  
예외없이 50,000 나온다.

2. volatile을 사용한 경우(50000이 나오지 않는다.)
A::::: END
C::::: END
B::::: END
D::::: END
E::::: END
43620

댓글 없음:

댓글 쓰기