java volatile 探究

一 同步问题

先看一个多线程的例子,地球人都知道,这样的代码一定会有问题

public class Test {
	static int x = 0;
	private static int thread_num = 1000;
	public static void add (){
		x++;
	}
	public static void main(String[] args) {
		Thread [] task = new Thread[thread_num];
		Thread addThread = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				add();
			}
		}
		);
		// 多个线程调用add
		for (int i =0;i < thread_num;i++){
			task[i]=new Thread(addThread);
			task[i].start();
		}
		// 等待所有线程执行完毕
		for (int i=0;i<thread_num;i++){
			try {
				task[i].join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		System.out.println("x is "+x);
	}
}


于是有人就说使用volatile修饰一下x,好的,我们用volatile修饰一下,代码如下:

public class Test {
	static volatile int x = 0;
	private static int thread_num = 1000;
	public static void add (){
		x++;
	}
	public static void main(String[] args) {
		Thread [] task = new Thread[thread_num];
		Thread addThread = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				add();
			}
		}
		);
		// 多个线程调用add
		for (int i =0;i < thread_num;i++){
			task[i]=new Thread(addThread);
			task[i].start();
		}
		// 等待所有线程执行完毕
		for (int i=0;i<thread_num;i++){
			try {
				task[i].join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		System.out.println("x is "+x);
	}
}

执行结果:

x is 998


二 volatile 与atomic的机制

加了volatile为什么还会出错呢?我们得从原子性和volatility说起。

 

Atomic operation:原子性是指在线程调度中不可以被中断的操作。

 

volatility: 对于多核的处理器,每个核都有自己的cache,因此每个核的变量对其他核是不可见(invisible)的,只有写到主存里才是所有核都可见的。而volatility就是一种保障多处理器间visibility的机制。如果将一个域声明为volatile,在jvm执行时会让处理器直接从主存(main memory)中读取数据(如果非volatile则会从cache中读取),写数据的时候也会直接写入主存。另外,多线程和多核cpu也不是一个概念,每个cpu都有自己的cache,每个线程也有自己的local storage,这里不讨论。

 

三 Atomicityvolatility 的区别

 

 二者是完全不同的概念:

原子操作是指不可中断,是指cpu将数据写入cache或从cache时读取时的不可中断。

对于一个非volatile域,对其的原子操作不需要立即写入主存,因此其他的线程也不一定能看到最新的值。

java中,原子操作和volatile没关系,两者一个是说线程调度不中断,一个是直接读写主存。


四 volatile不能保证同步问题所在

上述代码中volatile不能保证同步的原因是x++不是原子操作,在JVM的编译执行中最后是分成多步的,这里不对x++进行具体分析,我们将这么一个操作抽象成3步:

1 读取x

2 x加1

3 将x写回

由于volatile的修饰,因此我们对x的读写都是直接操作主存的,假设有两个线程并行操作,我们用三幅图表示,数字代表指令执行顺序,可能会出现如下情况:

java volatile 探究_第1张图片

                                                                                          图1


java volatile 探究_第2张图片

                                                                                             图2


java volatile 探究_第3张图片

                                                                                              图3

1中两个线程或同时或先后读取x=0

2中两个线程完成加1

3中两个线程先后写如x=1,预期结果应该是2,出错!


由于volatile的修饰,因此我们对x的读写都是直接操作主存的,假设有两个线程并行操作,可能会出现如下情况:

由上图知道,虽然volatile对主存进行操作,由于x++的非原子性,因此会导致最后结果的出错。

线程同步时,多个线程对变量访问时,volatile并不能保证同步,想要保证正确性,需要让线程使用synchronized同步。Synchronization机制保障将数据flush入主存中,因此用synchronization机制就不需要再声明volatile了。 


你可能感兴趣的:(java,java基础,volatile)