理解java中的volatile关键字

Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了
实现代码线程的安全性。Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized
块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。

volatile 写和读的内存语义
线程 A 写一个 volatile 变量,实质上是线程 A 向接下来将要读这个 volatile  变量的某个线程发出了(其对共享变量所在修改的)消息。

线程 B 读一个 volatile 变量,实质上是线程 B  接收了之前某个线程发出的(在写这个 volatile  变量之前对共享变量所做修改的)消息。

线程 A 写一个 volatile 变量,随后线程 B 读这个 volatile  变量,这个过程实质上是线程 A 通过主内存向线程 B  发送消息。


JMM(java内存模型)的支持原理

volatile用来确保将变量的更新操作通知到其它线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量时共享的,因此不会将该变量的操作与其它内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其它处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。严格限制编译器和处理器对 volatile  变量与普通变量的重排序,确保 volatile 的写-读和锁的释放-获取具有相同的内存语义。从编译器重排序规则和处理器内存屏障插入策略来看,只要 volatile  变量与普通变量之间的重排序可能会破坏 volatile  的内存语义,这种重排序就会被编译器重排序规则和处理器内存屏障插入策略禁止。编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

volatile变量对可见性的影响比volatile变量本身更为重要。当线程A首先写入一个volatile变量并且线程B随后读取该变量时,在写入volatile变量之前对A可见的所有变量的值,在B读取了volatile变量后,对B也是可见的。因此,从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量就相当于进入同步代码块。我们可以简单理解为当要读取一个volatile变量时总是伴随着先要到主内存刷新取到最新值,而写变量时会把值直接写到主内存以对其它线程可见,这些都是由JMM(java内存模型)禁止指令重排序做保证的。不建议过度依赖volatile变量提供的可见性。如果在代码中依赖volatile变量来控制状态的可见性,通常比使用锁的代码更脆弱,也更难理解。


volatile典型用法

仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。volatile变量的正确使用方式包括:确保它们自身状态的可见性,确保他们所引用对象的状态的可见性,以及标示一些重要的程序声明周期事件的发生,如初始化或关闭。

volatile boolean asleep;
...
   while(!asleep)
	countSomeSheep();

上面是volatile变量的一种典型用法:检查某个状态标记以判断是否退出循环。在这个示例中,线程试
图通过类似于数数的传统方法进入休眠状态。为了使这个示例能正确执行,asleep必须为volatile变量。否则,当asleep被另一个线程修改时,执行
判断的线程却发现不了。我们也可以用锁来确保asleep更新操作的可见性,但是这样将使代码变得更加复杂。
虽然volatile很方便,但也存在一些局限性。volatile变量通常用作某个操作完成发生中断的标志。尽
管volatile变量也可以用于表示其他的状态信息,但在使用时要非常小心。例如volatile的语义不足以
确保递增操作(count++)的原子性,除非你能确保只有一个线程对变量执行写操作。

加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。
当且仅当满足以下所有条件时,才应该使用volatile变量。

a,对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
b,该变量不会与其它状态变量一起纳入不变形条件中。
c,在访问变量时不需要加锁。


参考文献:

java并发编程实战,

深入理解JVM

Java volatile 关键字主要用于保证多线程环境下的变量可见性和禁止指令重排。在多线程环境下,多个线程可以同时访问同一个变量,如果不使用 volatile 关键字,可能会出现一个线程修改了变量的值,但另一个线程并没有看到这个修改的情况。这是因为在多线程环境下,每个线程有自己的 CPU 缓存,当一个线程修改了变量的值时,这个值可能只存在于这个线程的 CPU 缓存,而没有写入到主存。另一个线程访问这个变量时,可能会直接从自己的 CPU 缓存读取这个变量的值,而没有在主存重新读取,这就导致了可见性问题。 volatile 关键字可以解决这个可见性问题,它可以保证变量的修改对于所有的线程都是可见的。当一个变量被声明为 volatile 时,所有的写操作都会立即写入主存,而读操作也会直接从主存读取变量的值,而不是从 CPU 缓存读取。这样就保证了变量对于所有的线程都是可见的。 另外,volatile 关键字还可以禁止指令重排。在计算机,为了优化代码执行效率,编译器和 CPU 会对指令进行重排,但有些指令的顺序是不能改变的,否则就会出现问题。使用 volatile 关键字可以禁止编译器和 CPU 对指令进行重排,从而保证指令的顺序不会改变。 下面是一个使用 volatile 关键字的示例代码: ```java public class VolatileExample { private volatile int value; public void setValue(int value) { this.value = value; } public int getValue() { return value; } } ``` 在这个示例,value 变量被声明为 volatile,这保证了对于所有的线程,value 的修改都是可见的。在 setValue 方法,对 value 变量进行赋值操作时,这个操作会立即写入主存,而不是先写入 CPU 缓存。在 getValue 方法,直接从主存读取 value 变量的值,而不是从 CPU 缓存读取。这样就保证了 value 变量的可见性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值