volatile

 

一、变量自身具有下列特性。

  1. 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
  2. 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
  3. volatile变量的写-读可以实现线程之间的通信。
  4. 从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果:volatile写和

锁的释放有相同的内存语义;volatile读与锁的获取有相同的内存语义。

二、volatile写-读的内存语义

  • volatile写的内存语义如下:

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

  • volatile读的内存语义如下:

当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

 

2.1volatile写和volatile读的内存语义做个总结。

  • ·线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程

发出了(其对共享变量所做修改的)消息。

  • ·线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile

变量之前对共享变量所做修改的)消息。

  • ·线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过

主内存向线程B发送消息

2.2volatile的内存语义的实现

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。

三、锁的内存语义

  1. 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
  2. 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

3.1对锁释放和锁获取的内存语义做个总结。

  • ·线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
  • ·线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
  • ·线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。

3.2内存语义的实现

3.21.对公平锁和非公平锁的内存语义做个总结。

·公平锁和非公平锁释放时,最后都要写一个volatile变量state。

·公平锁获取时,首先会去读volatile变量。

·非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和volatile

写的内存语义。

 

3.2.2锁释放-获取的内存语义的实现至少有下面两种方式。

1)利用volatile变量的写-读所具有的内存语义。

2)利用CAS所附带的volatile读和volatile写的内存语义。

四、volatile和锁的区别

由于volatile仅仅保证对单个volatile变量的读/写具有原子性,而锁的互斥执行的特性可以

确保对整个临界区代码的执行具有原子性。在功能上,锁比volatile更强大;在可伸缩性和执行

性能上,volatile更有优势。

 

五、CAS

ava的compareAndSet()方法调用简称为CAS。JDK文档对该方法的说明如下:如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。此操作具有volatile读和写的内存语义。

 

这里我们分别从编译器和处理器的角度来分析,CAS如何同时具有volatile读和volatile写的内存语义。

1.编译器不会对volatile读与volatile读后面的任意内存操作重排序;编译器不会对volatile写与volatile写前面的任意内存操作重排序。组合这两个条件,意味着为了同时实现volatile读和volatile写的内存语义,编译器不能对CAS与CAS前面和后面的任意内存操作重排。

2.程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(Lock Cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。

intel的手册对lock前缀的说明如下。

1)确保对内存的读-改-写操作原子执行。(1)带有lock前

缀的指令在执行期间会锁住总线,使得其他处理器暂时无法通过总线访问内存。很显然,这会

带来昂贵的开销。(2)使用缓存锁定(Cache Locking)来保证指令执行的原子性。缓存锁定将大大降低lock前缀指令的执行开销。

2)禁止该指令,与之前和之后的读和写指令重排序。

3)把写缓冲区中的所有数据刷新到内存中。

上面的第2点和第3点所具有的内存屏障效果,足以同时实现volatile读和volatile写的内存

语义。

经过上面的分析,现在我们终于能明白为什么JDK文档说CAS同时具有volatile读和volatile写的内存语义了。

六、concurrent包的实现

6.1volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。

6.2由于Java的CAS同时具有volatile读和volatile写的内存语义,因此Java线程之间的通信现

在有了下面4种方式。

1)A线程写volatile变量,随后B线程读这个volatile变量。

2)A线程写volatile变量,随后B线程用CAS更新这个volatile变量。

3)A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。

4)A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

6.3如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式。

首先,声明共享变量为volatile。

然后,使用CAS的原子条件更新来实现线程之间的同步。

同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent

包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类

来实现的。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值