为什么volatile不能保证原子性?
为什么volatile能替代简单的锁,却不能保证原子性?这里面涉及volatile,是java中的一个我觉得这个词在Java规范中从未被解释清楚的神奇关键词,在Sun的JDK官方文档是这样形容volatile的:
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.
意思就是说,如果一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。
volatile似乎是有时候可以代替简单的锁,似乎加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性)。这不是互相矛盾吗?
不要将volatile用在getAndOperate场合,仅仅set或者get的场景是适合volatile的
不要将volatile用在getAndOperate场合(这种场合不原子,需要再加锁),仅仅set或者get的场景是适合volatile的。
volatile没有原子性举例:AtomicInteger自增
例如你让一个volatile的integer自增(i++),其实要分成3步:1)读取volatile变量值到local; 2)增加变量的值;3)把local的值写回,让其它的线程可见。这3步的jvm指令为:
|
1
2
3
4
|
mov 0xc(%r10),%r8d ; Loadinc %r8d ; Incrementmov %r8d,0xc(%r10) ; Storelock addl $0x0,(%rsp) ; StoreLoad Barrier |
注意最后一步是内存屏障。
什么是内存屏障(Memory Barrier)?
内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的结果)。编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。
内存屏障(memory barrier)和volatile什么关系?上面的虚拟机指令里面有提到,如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。这意味着如果你对一个volatile字段进行写操作,你必须知道:1、一旦你完成写入,任何访问这个字段的线程将会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。
volatile为什么没有原子性?
明白了内存屏障(memory barrier)这个CPU指令,回到前面的JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失。下面的测试代码可以实际测试voaltile的自增没有原子性:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
private static volatile long _longVal = 0; private static class LoopVolatile implements Runnable { public void run() { long val = 0; while (val < 10000000L) { _longVal++; val++; } } } private static class LoopVolatile2 implements Runnable { public void run() { long val = 0; while (val < 10000000L) { _longVal++; val++; } } } private void testVolatile(){ Thread t1 = new Thread(new LoopVolatile()); t1.start(); Thread t2 = new Thread(new LoopVolatile2()); t2.start(); while (t1.isAlive() || t2.isAlive()) { } System.out.println("final val is: " + _longVal); }Output:------------- final val is: 11223828final val is: 17567127final val is: 12912109 |
volatile没有原子性举例:singleton单例模式实现
这是一段线程不安全的singleton(单例模式)实现,尽管使用了volatile:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class wrongsingleton { private static volatile wrongsingleton _instance = null; private wrongsingleton() {} public static wrongsingleton getInstance() { if (_instance == null) { _instance = new wrongsingleton(); } return _instance; }} |
下面的测试代码可以测试出是线程不安全的:
原因自然和上面的例子是一样的。因为volatile保证变量对线程的可见性,但不保证原子性。
附:正确线程安全的单例模式写法:
|
1
2
3
4
5
6
7
8
9
|
@ThreadSafepublic class SafeLazyInitialization { private static Resource resource; public synchronized static Resource getInstance() { if (resource == null) resource = new Resource(); return resource; } } |
另外一种写法:
|
1
2
3
4
5
|
@ThreadSafepublic class EagerInitialization { private static Resource resource = new Resource(); public static Resource getResource() { return resource; } } |
延迟初始化的写法:
|
1
2
3
4
5
6
7
8
9
|
@ThreadSafepublic class ResourceFactory { private static class ResourceHolder { public static Resource resource = new Resource(); } public static Resource getResource() { return ResourceHolder.resource ; } } |
二次检查锁定/Double Checked Locking的写法(反模式)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class SingletonDemo { private static volatile SingletonDemo instance = null;//注意需要volatile private SingletonDemo() { } public static SingletonDemo getInstance() { if (instance == null) { //二次检查,比直接用独占锁效率高 synchronized (SingletonDemo .class){ if (instance == null) { instance = new SingletonDemo (); } } } return instance; }} |
为什么AtomicXXX具有原子性和可见性?
就拿AtomicLong来说,它既解决了上述的volatile的原子性没有保证的问题,又具有可见性。它是如何做到的?当然就是上文《非阻塞同步算法与CAS(Compare and Swap)无锁算法》提到的CAS(比较并交换)指令。 其实AtomicLong的源码里也用到了volatile,但只是用来读取或写入,见源码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class AtomicLong extends Number implements java.io.Serializable { private volatile long value; /** * Creates a new AtomicLong with the given initial value. * * @param initialValue the initial value */ public AtomicLong(long initialValue) { value = initialValue; } /** * Creates a new AtomicLong with initial value {@code 0}. */ public AtomicLong() { } |
其CAS源码核心代码为:
|
1
2
3
4
5
6
7
8
9
|
int compare_and_swap (int* reg, int oldval, int newval) { ATOMIC(); int old_reg_val = *reg; if (old_reg_val == oldval) *reg = newval; END_ATOMIC(); return old_reg_val;} |
虚拟机指令为:
|
1
2
3
4
|
mov 0xc(%r11),%eax ; Loadmov %eax,%r8d inc %r8d ; Incrementlock cmpxchg %r8d,0xc(%r11) ; Compare and exchange |
因为CAS是基于乐观锁的,也就是说当写入的时候,如果寄存器旧值已经不等于现值,说明有其他CPU在修改,那就继续尝试。所以这就保证了操作的原子性。

转载自:http://www.cnblogs.com/Mainz/p/3556430.html#
为什么volatile不能保证原子性?的更多相关文章
- 为什么volatile不能保证原子性而Atomic可以?
在上篇<非阻塞同步算法与CAS(Compare and Swap)无锁算法>中讲到在Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值 ...
- volatile之一--volatile不能保证原子性
Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这 ...
- 【转】为什么volatile不能保证原子性而Atomic可以?
直接上好文链接!!! 为什么volatile不能保证原子性而Atomic可以?
- Java并发编程之验证volatile不能保证原子性
Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...
- Volatile不保证原子性(二)
Volatile不保证原子性 前言 通过前面对JMM的介绍,我们知道,各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后在写回到主内存中的. 这就可能存在一个线程AAA修改 ...
- Java线程-volatile不能保证原子性
下面是一共通过volatile实现原子性的例子: 通过建立100个线程,计算number这个变量最后的结果. package com.Sychronized; public class Volatil ...
- volatile不能保证原子性
1.看图自己体会 2.体会不了就给你个小程序 package cs.util; public class VolatileDemo { private volatile int count =0; p ...
- JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁
问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...
- 为什么volatile能保证有序性不能保证原子性
对于内存模型的三大特性:有序性.原子性.可见性. 大家都知道volatile能保证可见性和有序性但是不能保证原子性,但是为什么呢? 一.原子性.有序性.可见性 1.原子性: (1)原子的意思代表着-- ...
随机推荐
- Lombok在工程中的使用
在公司的项目中应用了Lombok插件,在idea中需要启用Annotation Processors中的Enable annotation processing选项,之后才能使用Lombok的各个注解 ...
- Flask的数据库连接池 DBUtils
Flask是没有ORM的操作的,如果在flask中连接数据库有两种方式 一.pymysql 二.SQLAlchemy 是python操作数据库的以一个库,能够进行orm映射官网文档 sqlchemy ...
- 用字典给Model赋值
用字典给Model赋值 此篇教程讲述通过runtime扩展NSObject,可以直接用字典给Model赋值,这是相当有用的技术呢. 源码: NSObject+Properties.h 与 NSObje ...
- Django学习---抽屉热搜榜分析【all】
Python实例---抽屉热搜榜前端代码分析 Python实例---抽屉后台框架分析 Python学习---抽屉框架分析[点赞功能分析] Python学习---抽屉框架分析[数据库设计分析]18031 ...
- buff/cache 内容释放
oscache远程服务器特别卡,top命令查看获得 buff/cache 占据内存特别大,使用以下命令清理缓存: swap清理: swapoff -a && swapon -a 注意: ...
- Spring 如何在 WEB 应用中使用
1. Spring 如何在 WEB 应用中使用 ? 1). 需要额外加入的 jar 包: spring-web-4.0.0.RELEASE.jar spring-webmvc-4.0.0.RELEAS ...
- 简单的dp加贪心
题目链接:传送门 这个题目让我纠结了好久,之后恍然大悟是求最长的递减序列,并加上贪心的算法,如果有大于两个的发射系统,应该判断使导弹的高度与此时个个发射系统的高度比较,选取高度差最小的去执行这次的拦截 ...
- 4-5 R语言函数 split
#split根据因子或因子列表将 向量或其他对象分组 #通常与lapply一起使用 #split(参数):split(向量/列表/数据框,因子/因子列表) > x <- c(rnorm(5 ...
- [IDE123] Intellij Idea 快捷键
Ctrl+Shift+N,可以快速打开文件 Ctrl+Shift + Enter,语句完成“!”,否定完成,输入表达式时按 “!”键Ctrl+E,最近的文件Ctrl+Shift+E,最近更改的文件Sh ...
- PHP的Reflection反射机制
更多内容推荐微信公众号,欢迎关注: 原文地址: http://www.nowamagic.net/php/php_Reflection.php PHP5添加了一项新的功能:Reflection.这个功 ...