天天看点

Java Volatile学习

[size=medium]Volatile

Volatile的英文解释:adj.易变的,不稳定的.

工作内存Working Memory: 线程私有空间,在虚拟机栈内存里面,由每个线程独享。

主内存Main Memory: 多个线程共享,在堆内存里面。

对于Volatile类型的变量来说,上一次写入操作的结果对下一次读取操作是肯定可见的。在写入volatile变量值之后,CPU缓存中的内容会被写回主存,在读取volatile变量时,CPU缓存中的对应内容被设置为失效状态,重新从主存中进行读取。将变量声明为volatile相当于为单个变量的读取和写入添加了同步操作。但是volatile在使用时不需要利用锁机制,因此性能要由于synchronized关键词。[/size]

public class Worker {
	private volatile boolean done;
	public void setDone(boolean done) {
		this.done = done;
	}
	public void work() {
		while (!done) {
			//do something
		}
	}
}
           

[size=medium]对于上面的例子,当线程A调用lWorker类的对象的work方法,开始执行具体的任务。在适当的时候线程B会调用同一Work类的对象的setDone方法来声明终止任务的执行。把done变量声明为volatile是很重要的。只有这样才能保证线程B对done变量所做的修改对线程A的后续操作是可见的。否则,线程A可能由于无法看到done变量值的变化而一直运行下去。

但是虽然volatile关键词使用简单,但是由于在实现时没有锁机制的存在,volatile关键词的适用场景是受限的。比如对于下面的例子:[/size]

public class IdGenerator {
	private int value = 0;
	public int getNext() {
		return value++;
	}
}
           

[size=medium]注释: 虽然getNext方法只有一行代码,但是这一行代码对应的字节码指令却是7条。

如果只是把value声明为volatile是不够的,仍然会出现问题。这是因为写入的value的正确值依赖于value的当前值,而当前值有可能是不正确的。假设线程A获取了value的当前值1却发生了线程切换,如果线程B把value改成2后,线程A才获得了执行,这个时候A所持有的当前值1就已经不是正确的了。[color=darkred][b]当要写入的新值与当前值没有关系时,使用volatile就足够了。[/b][/color]

原子操作:

在Java中,对于非long型和double型的域的读取和写入操作是原子操作。对象引用的读取和写入操作也是原子操作。比如读取一个int型的域时,该域对应的内存地址中的32位的内容会被完整读取,在读取过程中不会被其他线程所打断。在写入操作时也不会被打断。在写入非volatile的long型和double型的域的值时,分成两次操作来完成。一个long型或double型的域的长度是64位,每次写入32位。在一个线程写入了long型或double型的域的前32位之后,在写入后32位之前,另外一个线程有可能访问这个域的值,从而读取只完成部分写入操作的错误值。因此在多线程程序中使用long型和double型的共享变量时,需要把变量声明为volatile,以保证读取和写入的完整性。

另外注意这里说的类型时基本类型,并不是封装类型,封装类型事实上是object.

可见性:[/size]

继续阅读