volatile不能保证原子性,atomic不仅保证可见性还有原子性CAS分析
给一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。volatile似乎是有时候可以代替简单的锁,似乎加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性)。这不是互相矛盾吗?
不要将volatile用在getAndOperate场合(这种场合不原子,需要再加锁),仅仅set或者get的场景是适合volatile的。
例如你让一个volatile的integer自增(i++),其实要分成3步:1)读取volatile变量值到local; 2)增加变量的值;3)把local的值写回,让其它的线程可见。这3步的jvm指令为:
mov
0xc
(%r10),%r8d ; Load
inc %r8d ; Increment
mov %r8d,
0xc
(%r10) ; Store
lock addl $
0x0
,(%rsp) ; StoreLoad Barrier
JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失
重点:为什么AtomicXXX具有原子性和可见性?
1、其实AtomicLong的源码里也用到了volatile,但只是用来读取或写入,见源码:(atomicInteger一样)
public
class
AtomicLong
extends
Number
implements
java.io.Serializable {
private
volatile
long
value;
public
AtomicLong(
long
initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicLong with initial value {@code 0}.
*/
public
AtomicLong() {
}
上面的源码说明了它的可见性是没有问题的,
2、还有一个要点是,atomic(如atomicInteger)使用的是CAS指令来保证变量的原子性的;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;// 注意是静态的
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));// 反射出value属性,获取其在内存中的位置
} catch (Exception ex) { throw new Error(ex); }
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
额外:
简单的来说,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。
其CAS源码核心代码为:
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;
}
利用Unsafe类的JNI方法实现,使用CAS指令,可以保证读-改-写是一个原子操作。compareAndSwapInt有4个参数,this - 当前AtomicInteger对象,Offset - value属性在内存中的位置(需要强调的是不是value值在内存中的位置),expect - 预期值,update - 新值,根据上面的CAS操作过程,当内存中的value值等于expect值时,则将内存中的value值更新为update值,并返回true,否则返回false。在这里我们有必要对Unsafe有一个简单点的认识,从名字上来看,不安全,确实,这个类是用于执行低级别的、不安全操作的方法集合,这个类中的方法大部分是对内存的直接操作,所以不安全,但当我们使用反射、并发包时,都间接的用到了Unsafe
既然有问题,就需要进行优化:
public static class MyLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public boolean lock() {
return locked.compareAndSet(false, true);
}
}
locked变量不再是boolean类型而是AtomicBoolean。这个类中有一个compareAndSet()方法,它使用一个期望值和AtomicBoolean实例的值比较,和两者相等,则使用一个新值替换原来的值。在这个例子中,它比较locked的值和false,如果locked的值为false,则把修改为true。
如果值被替换了,compareAndSet()返回true,否则,返回false。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(55);//55
int addAndGet = atomicInteger.addAndGet(23);//78
boolean compareAndSet = atomicInteger.compareAndSet(78, 43);//true 都是78,则更换为43
int i = atomicInteger.get();//43
}
cas缺点
虽然使用CAS可以实现非阻塞式的原子性操作,但是会产生ABA问题,关于ABA问题:
有ABA问题(即在更新前的值是A,但在操作过程中被其他线程更新为B,又更新为 A),这时当前线程认为是可以执行的,其实是发生了不一致现象,如果这种不一致对程序有影响(真正有这种影响的场景很少,除非是在变量操作过程中以此变量为标识位做一些其他的事,比如初始化配置),则需要使用AtomicStampedReference(除了对更新前的原值进行比较,也需要用更新前的 stamp标志位来进行比较)。
总结:
可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题
volatile不能保证原子性,atomic不仅保证可见性还有原子性CAS分析相关推荐
- volatile关键字——保证并发编程中的可见性、有序性
文章目录 一.缓存一致性问题 二.并发编程中的三个概念 三.Java线程内存模型 1.原子性 2.可见性 3.有序性 四.深入剖析volatile关键字 1.volatile关键字的两层语义 2.vo ...
- 如何保证线程安全有序性_线程安全性-原子性-可见性-有序性
一.相关定义: 线程安全类:当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些进程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安 ...
- 内存可见性和原子性:Synchronized和Volatile的比较
Java多线程之内存可见性和原子性:Synchronized和Volatile的比较 [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article ...
- 并发编程-04线程安全性之原子性Atomic包的4种类型详解
文章目录 线程安全性文章索引 脑图 概述 原子更新基本类型 Demo AtomicBoolean 场景举例 原子更新数组 Demo 原子更新引用类型 Demo 原子更新字段类型 使用注意事项: Dem ...
- java安全编码指南之:可见性和原子性
文章目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有 ...
- ThreadLocal、Volatile、synchronized、Atomic关键字扫盲
前言 对于ThreadLocal.Volatile.synchronized.Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点.区别 ...
- java计算时间差_JAVA并发编程三大Bug源头(可见性、原子性、有序性),彻底弄懂...
原创声明:本文转载自公众号[胖滚猪学编程] 某日,胖滚猪写的代码导致了一个生产bug,奋战到凌晨三点依旧没有解决问题.胖滚熊一看,只用了一个volatile就解决了.并告知胖滚猪,这是并发编程导致的 ...
- 三大性质总结:原子性,有序性,可见性
转载自:https://www.jianshu.com/p/cf57726e77f2 1. 三大性质简介 在并发编程中分析线程安全的问题时往往需要切入点,那就是两大核心:JMM抽象内存模型以及happ ...
- 原子性、有序性和可见性解释
概念解释: 原子性(Atomicity) 由 Java 内存模型来直接保证的原子性变量操作包括 read.load.assign.use.store 和 write.大致可以认为基本数据类型的操作是原 ...
- 线程安全问题的本质详解: 原子性、有序性、可见性
内容导航 volatile的作用 什么是可见性 volatile源码分析 一.volatile的作用 在多线程中,volatile和synchronized都起到非常重要的作用,synchronize ...
最新文章
- Linux2.6内核中链表的实现
- Windows核心编程 第十五章 在应用程序中使用虚拟内存
- 【开发环境】戴尔电脑系统重装 ( 下载 Dell OS Recovery Tool 工具 | 使用 Dell OS Recovery Tool 工具制作 U 盘系统 | 安装系统 )
- MongoDB安装步骤
- 2.IDA-数据显示窗口(反汇编窗口、函数窗口、十六进制窗口)
- 物联网卡linux,Server Develop (六) Linux epoll总结
- bootstrap 模态框无法使用_模态窗 Modal Window - 产品中的??注意力设计
- VC下设置Excel单元格的边框
- macOS安装配置Java
- Android 第二章 本地文件的读写
- 为什么越来越多的人尝试做自媒体
- Java直连Access
- excel合并两列内容_excel新手问题:怎么把两列数据合并到一起?用这个公式
- 也就整了一万字的「数据指标体系」指南。
- 基于JavaSwing+MySQL实现的超市商品管理系统
- java 流读取图片供前台显示
- Excel 2010 SQL应用116 分组统计之GROUP BY续
- 【RBF预测】基于时空 RBF-NN 实现混沌时间序列预测附matlab代码
- SQL 发送Email
- margin:0 auto是什么意思