JAVA并发编程4_线程同步之volatile关键字

上一篇博客JAVA并发编程3_线程同步之synchronized关键字中讲解了JAVA中保证线程同步的关键字synchronized,其实JAVA里面还有个较弱的同步机制volatile。volatile关键字是JAVA中的轻量级的同步机制,用来将变量的更新操作同步到其他线程。从内存可见性的角度来说,写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块。

旧的内存模型:保证读写volatile都直接发生在main memory中。

在新的内存模型下(1.5)对volatile的语义进行了修补和增强:如果当线程 A 写入 volatile 变量 V 而线程 B 读取 V 时,那么在写入 V 时,A 可见的所有变量值现在都可以保证对 B 是可见的。

一句话:volatile保证可见性,但不能保证原子性。

原子性的:一组语句作为一个不可分割的单元被执行。任何一个执行同步代码块的线程,都不可能看到有其他线程正在执行由同一个锁保护的同步代码块。volatile变量的非原子性最容易被忽略。

可见性:指一个线程修改了一个共享变量的值,其他线程能够立即得知这个修改。

volatile的非原子性

变量被定义为volatile并不能保证对其所有操作是原子的,由于非原子性,因此volatile并不能保证多线程并发的安全性。如下面的代码:

public class Test implements Runnable{
	public volatile int race = 0;
	@Override
	public void run() {
		increase();
	}
	private void increase() {
		race ++;
		
	}
	public static void main(String[] args) {
		Test t = new Test();
		Thread [] threads = new Thread[1000];
		for (int i = 0; i < 1000; i++) {
			threads[i] = new Thread(t);
			threads[i].start();
		}
		while (Thread.activeCount() > 1) {
			Thread.yield();
		}
		// 保证打印的时候1000个线程都已经执行完毕
		System.out.println(t.race);
	}
}

这段代码开启了1000个线程,对race变量进行自增操作。理论上,线程安全的话,执行结果应该是1000。但实际上执行得到的结果都是一个小于1000的值。

分析一下上面案的代码,问题就出在了race++这句代码。它不是原子操作。这句代码实际上是分为三个操作的:读取race的值、进行加1操作、写入新的值。

显然可以看出来,将变量定义成vilatile也不能保证原子性:

线程1先读取了变量race的原始值,然后线程1被阻塞了;线程2也去读取变量race的原始值,然后进行加1操作,并把+1后的值写入工作内存,最后写入主存,然后线程1接着进行加1操作,由于已经读取了race的值,此时在线程1的工作内存中race的值仍然是之前的值,所以线程1对race进行加1操作后的值和刚才一样,然后将这个值写入工作内存,最后写入主存。这样就出现了两个线程自增完后其实只加了一次。究其原因是因为volatile不能保证原子性。

可以将自增操作改为同步代码块即可解决。

private synchronized void increase() {
		race ++;
	}

volatile的可见性

一个线程修改了某个volatile变量的值,这新值对其他线程来说是立即可见的。

boolean ready;
// thread 1
while (!ready) {
       doSomthing();
}
// thread2
ready = true;

这是销毁线程的通用方法。但是存在问题是ready变量改为true还没来得及写入主存,就转到其他线程执行了,这时还会进入循环。这时,volatile的作用就体现出来了。volatile变量保证了他在一个线程里面修改后会立即被其他线程得知。

volatile变量的使用场景这篇文章写得很详细:Java 理论与实践: 正确使用 Volatile 变量

posted @ 2015-05-26 18:08  浩荡乾坤  阅读(1809)  评论(0编辑  收藏  举报