谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题
于谈论高并发(十一)几个自旋锁的实现(五岁以下儿童)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用AtomicStampedReference原子变量而不是使用AtomicReference是由于这个实现中等待队列的同一个节点具备不同的状态,而同一个节点会多次进出工作队列,这就有可能出现出现ABA问题。
熟悉并发编程的同学应该知道CAS操作存在ABA问题。我们先看下CAS操作。
CAS(Compare and Swap) 比較并交换操作是一个三元操作: 目标地址的值T(arget)。期望值E(xpected),实际值R(eal),
1. 仅仅有当目标值T == 期望值E时。才会把目标值T设置为实际值R,否则不改变目标值
2. 无论目标值是否改变,都返回之前的目标值T
类似例如以下的逻辑:
package com.zc.lock;
public class CAS {
private int value;
public synchronized int get(){
return value;
}
public synchronized int compareAndSwap(int expected, int real){
int oldValue = value;
if(value == expected){
value = real;
}
return oldValue;
}
public synchronized boolean compareAndSet(int expected, int real){
return (expected == compareAndSwap(expected, real));
}
}
CAS仅仅比較期望值和目标值是否相当。相当就设置新值。那么ABA问题就来了:
1. 因为CAS仅仅是值比較,比方目标是A, 期望值也是A, 那么CAS操作会成功。可是这时候目标A可能不是原来的那个A了。它可能是A变成了B,再变成了A。
所以叫ABA问题,非常形象。
ABA问题可能会使程序出错。比方限时有界队列锁中的节点有几个状态。尽管引用值是A。可是可能对象的状态已经变了,这时候的A实际已经不是原来的A了
2. 须要注意的是ABA问题不是说CAS操作的过程中A变成了ABA,CAS操作是原子操作,不会被打断。ABA问题场景例如以下:
先获取了A的值。然后再CAS(A, R), 这时候CAS中的A实际指向的对象的状态可能和它刚获得的时候的状态已经发送了改变。
</pre><pre name="code" class="java">A a = ref.get();
// 依据a的状态做一些操作
// do something
// CAS,这时候会出现ABA问题,a指向的对象可能已经变了
ref.compareAndSet(a, b)
解决ABA问题方法就是给状态设置时间戳,这是并发中加乐观锁的常见做法。假设状态的时间戳发生了改变,证明已经不是原来的对象了,所以操作失败
// 用int做时间戳
AtomicStampedReference<QNode> tail = new AtomicStampedReference<CompositeLock.QNode>(null, 0);
int[] currentStamp = new int[1];
// currentStamp中返回了时间戳信息
QNode tailNode = tail.get(currentStamp);
tail.compareAndSet(tailNode, null, currentStamp[0], currentStamp[0] + 1)
以下我们来看一下java.util.concurrent.atomic.AtomicStampedReference的源码是怎样实现的。
以下代码来自JDK1.7,条理非常清晰,实现有几个要点:
1. 创建一个Pair类来记录对象引用和时间戳信息,採用int作为时间戳,实际使用的时候时间戳信息要做成自增的,否则时间戳假设反复,还会出现ABA的问题。这个Pair对象是不可变对象,全部的属性都是final的。 of方法每次返回一个新的不可变对象
2. 使用一个volatile类型的引用指向当前的Pair对象。一旦volatile引用发生变化。变化对全部线程可见
3. set方法时,当要设置的对象和当前Pair对象不一样时。新建一个不可变的Pair对象
4. compareAndSet方法中,仅仅有期望对象的引用和版本号号和目标对象的引用和版本号好都一样时,才会新建一个Pair对象,然后用新建的Pair对象和原理的Pair对象做CAS操作
5. 实际的CAS操作比較的是当前的pair对象和新建的pair对象,pair对象封装了引用和时间戳信息
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
public void set(V newReference, int newStamp) {
Pair<V> current = pair;
if (newReference != current.reference || newStamp != current.stamp)
this.pair = Pair.of(newReference, newStamp);
}
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
private static final long pairOffset =
objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
版权声明:本文博客原创文章。博客,未经同意,不得转载。
谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题的更多相关文章
- Java并发—原子类,java.util.concurrent.atomic包(转载)
原子类 Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中 的原子操作类提供了一种用法简单.性能高效.线程安全地更新一个变量 ...
- 高并发编程基础(java.util.concurrent包常见类基础)
JDK5中添加了新的java.util.concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全性,所以这种方法 ...
- 聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类
这篇说说java.util.concurrent.atomic包里的类,总共12个.网上有非常多文章解析这几个类.这里挑些重点说说. watermark/2/text/aHR0cDovL2Jsb2cu ...
- 原子类java.util.concurrent.atomic.*原理分析
原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...
- java并发编程:线程安全管理类--原子包--java.util.concurrent.atomic
java.util.concurrent.atomic 的描述 AtomicBoolean 可以用原子方式更新的 boolean 值. AtomicInteger 可以用原子方式更新的 int 值. ...
- 并发之java.util.concurrent.atomic原子操作类包
15.JDK1.8的Java.util.concurrent.atomic包小结 14.Java中Atomic包的原理和分析 13.java.util.concurrent.atomic原子操作类包 ...
- Java多线程:CAS与java.util.concurrent.atomic
锁的几种概念 悲观锁 总是假设最坏的情况,每次获取数据都认为别人会修改,所以拿数据时会上锁,一直到释放锁不允许其他线程修改数据.Java中如synchronized和reentrantLock就是这种 ...
- java.util.concurrent.atomic 类包详解
java.util.concurrent包分成了三个部分,分别是java.util.concurrent.java.util.concurrent.atomic和java.util.concurren ...
- java.util.concurrent.atomic 包详解
Atomic包的作用: 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心: Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作 关于CAS compar ...
随机推荐
- 【ECSHOP插件】商品颜色尺寸仿淘宝选择功能免费发布
先放效果图,如此实用的功能是不是迫不及待的要添加到自己的网店中了呢 牵涉到的修改文件(default模板为例) /themes/default/style.css /themes/default/ ...
- docker 现实---中小企业docker环境结构(五)
docker对于中小企业,设定paas他没有足够的能量,没有必要为,个人二手sandbox实用性和小点.我个人觉得,中小企业可以使用docker要规范发展.测试.生产环境. 他画了一个简单的图表: d ...
- Web服务器Raspkate的RESTful API
基于轻量型Web服务器Raspkate的RESTful API的实现 在上一篇文章中,我们已经了解了Raspkate这一轻量型Web服务器,今天,我们再一起了解下如何基于Raspkate实现简单的RE ...
- FusionCharts简单教程---建立第一个FusionCharts图形
由于项目需求需要做一个报表,选择FusionCharts作为工具使用.由于以前没有接触过报表,网上也没有比较详细的fusionCharts教程,所以决定好好研究FusionCharts,同时做一个比较 ...
- Quartz.NET学习系列
Quartz.NET它是一个开源的任务调度引擎,对于周期性任务,持久性任务提供了很好的支持,并且支持持久性.集群等功能. 这是什么对我来说Quartz.NET学习记录: 源代码下载http://yun ...
- PHPCMS V9{loop subcat(0,0,0,$siteid) $r}怎么解释?
{loop subcat(0,0,0,$siteid) $r}{/loop} /** * 获取子栏目 * @param $parentid 父级id * @param $type 栏目类型 * ...
- Cordova探险系列(三)
自从3.0之后.Cordova默认是关闭全部关于设备原生特性功能的,所以我们要通过加入插件来启动原生特性. 这里以Accelerometer(加速度感应器)为例,来学习怎样使用设备原生特性. 1.加入 ...
- F5当刷新页面,出现“要再次显示此页,web该浏览器,你曾经有过发送消息再次提交...点击重试",如何防止此对话框解决方案的出现,
如何取消刷新页面弹出"重试"对话? 找了好多类似的问题都没有我想要的答案,请大家看清楚再回答. 比方说,登录过程中,成功之后转向还有一个页面success.jsp, 这时,假设刷新 ...
- hdu 5094 Maze(水搜索)
题目意思:说有一个人在(1,1) 他的目标点在(n,m) 每次是4方向的移动: 限制条件:有的各自之间有墙 或者门,强不可通过,有对应的要钥匙可以开启这个类型的所有门: 问题:求最少步骤数(和): 类 ...
- c#操作appsettiongs
try { //指定要修改的配置文件的路径 Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWe ...