本文主要介绍jdk中的原子类、ABA问题以及多个变量之间的安全访问。
原子类中核心的一个语法就是CAS操作,而这个操作封装在Unsafe类中,典型的应用如下代码
// 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"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
这个是AtomicInteger类的部分代码,其中valueOffset的值很关键,cas操作都要依赖它的,看下cas操作的代码
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
它有四个参数,this表示实例,valueOffset可看做是value的地址,expect表示期望值,update表示更新值,要表达的意思就是valueOffset处的值等于expect就用update更新,这个操作是在硬件级别上实现的。
典型的应用如下
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return current;
}
}
这种应用很普遍,它是实现非阻塞算法的基础,但是它也有一个特用的问题,叫ABA问题,这个后面在说。
AtomicReference类也是一个常用类,这是针对所有类的一个原子操作的实现,原理和AtomicInteger类似;JDK中也提供了原子的域更新器,可以更新指定类的指定域名,但是原理还是如AtomicInteger类的cas一样,在ConcurrentLinkedQueue中有典型的应用。
现在来说说ABA问题,非阻塞算法的思路是先get一个变量的值,然后执行cas操作,如果失败重复以上的两步操作,成功就返回,问题就在get操作和cas操作之间,一个线程执行了get后,由于线程的交替,另一个线程改变了执行环境,第一个线程执行cas的时候会得到错误的结果。这种问题在链表的操作中比较典型,想具体了解请看这边文章
http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html ,(有图有描述)
在贴一段代码
public class ABATest {
private static AtomicInteger atomicInt = new AtomicInteger(100);
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);
public static void main(String[] args) throws InterruptedException {
// testAtomicInteger();
testAtomicStampedReference();
}
// 出现ABA问题
public static void testAtomicInteger(){
Thread intT1 = new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInt.compareAndSet(100, 101);
atomicInt.compareAndSet(101, 100);
System.out.println("intT1 over");
}
});
// 在线程intT2获得oldValue,执行cas之前的时间段,线程intT1修改atomicInt两次,但intT2的cas操作还是成功执行了
Thread intT2 = new Thread(new Runnable() {
public void run() {
int oldValue=atomicInt.get();
System.out.println("intT2 start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
boolean c3 = atomicInt.compareAndSet(oldValue, 101);
System.out.println(c3); // true
}
});
intT1.start();
intT2.start();
}
// 可以有效避免ABA问题
public static void testAtomicStampedReference(){
Thread refT1 = new Thread(new Runnable() {
public void run(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
System.out.println("refT1 over");
}
});
// cas操作之前atomicStampedRef被修改,那么cas操作将失败
Thread refT2 = new Thread(new Runnable() {
public void run() {
int stamp = atomicStampedRef.getStamp();
System.out.println(stamp);
System.out.println("refT2 start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
System.out.println(atomicStampedRef.getStamp());
boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(c3); // false
}
});
refT1.start();
refT2.start();
}
}
这段代码中提到的ABA问题只是演示,它不会出现错误的结果。
在实现线程安全时,不可变类是一个很重要的概念,一个不可变的类的要求:对象的状态不可修改,对象的域都是final的,对象被正确的构造(不会发生this引用溢出)。多线程访问不可变对象一定是线程安全的,这也常应用于多个变量安全性保证上。如下代码保证两个变量的不变性约束
public class CasNumberRange {
// 将多个变量封装到一个对象中去
private class IntPair{
final int lower;
final int upper;
public IntPair(int lower, int upper){
this.lower=lower; // 不变约束:lower <= upper
this.upper=upper;
}
}
private final AtomicReference<IntPair> values=new AtomicReference<IntPair>(new IntPair(0,0));
public int getLower(){
return values.get().lower;
}
public int getUpper(){
return values.get().upper;
}
public void setLower(int i){
while(true){
IntPair oldValue=values.get();
if(i>oldValue.upper){
throw new IllegalArgumentException("Can't set lower to "+i+" > upper");
}
IntPair newValue=new IntPair(i,oldValue.upper);
if(values.compareAndSet(oldValue, newValue))
return;
}
}
public void setUpper(int i){
while(true){
IntPair oldValue=values.get();
if(i<oldValue.lower){
throw new IllegalArgumentException("Can't set upper to "+i+" < lower");
}
IntPair newValue=new IntPair(oldValue.lower,i);
if(values.compareAndSet(oldValue, newValue))
return;
}
}
}
在有些情况下,可以将多个变量封装在一个不可变类中实现线程安全。
分享到:
相关推荐
Java 多线程与并发(8_26)-JUC原子类_ CAS, Unsafe和原子类详解
原子类css,提高css开发效率
电子在库仑场中运动氢原子类氢原子.ppt
Java concurrency之AtomicReference原子类_动力节点Java学院整理,动力节点口口相传的Java黄埔军校
在JDK7包括7之前,java原子类有12个,图片如下,有些资料说有13个,多出来的是 AtomicBooleanArray 类,可是我在JDK8之前的源码里并没有发现有这个类,当然我也没去8以上的版本去看,所以这里不确定这个类到底在哪个...
Java concurrency之AtomicLongFieldUpdater原子类_动力节点Java学院整理,动力节点口口相传的Java黄埔军校
Java concurrency之AtomicLong原子类_动力节点Java学院整理,动力节点口口相传的Java黄埔军校
Java concurrency之AtomicLongArray原子类_动力节点Java学院整理,动力节点口口相传的Java黄埔军校
原子类测试,Blog中的说明信息
主要介绍了深入了解Java atomic原子类的使用方法和原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
一个模块化的Less基础类库,包含Reset、栅格系统、原子类、常用class和浏览器兼容等模块。 ##使用: 首先在你的less里引入 coffce.less,再根据你的需要,调用各个模块的init初始化模块 npm install coffce-less // ...
原子类 java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int...
引言JavaGuide :一份涵盖大部分Java程序员所需要掌握的核心知识。star:45159,替他宣传一下子这位大佬,总结的真好!我引用这位大佬的文章,因为
提出了实验中可行的利用部分纠缠态实现多原子类猫态的离物传送。在此方案中,离物传送多原子纠缠态的成功几率等于作为量子通道的部分纠缠态的较小叠加系数的模的平方的两倍,即,利用此类纠缠态作为量子通道传送实现...
主要介绍了Java多线程Atomic包操作原子变量与原子类详解,简单介绍了Atomic,同时涉及java.util.concurrent中的原子变量,Atomic类的作用等相关内容,具有一定参考价值,需要的朋友可以了解下。
npt-时间码用于处理正常播放时间(NPT)时间码的原子类。地位安装$ npm install npt-timecode用法 const { Timecode } = require ( 'npt-timecode' )const timecode = Timecode . from ( '00:00:30.456-00:01:00.789...
主要介绍了java JUC原子类基本类型详解的相关资料,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
原子操作 四种原子更新方式文档,分别是原子更新基本类型,原子更 新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装 类