AtomicStampedReference解决CAS机制中ABA问题
硬件中存在并发操作的原语,从而在硬件层面提升效率。在intel的CPU中,使用cmpxchg指令。在Java发展初期,java语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着java不断的发展,Java本地方法(JNI)的出现,使得java程序越过JVM直接调用本地方法提供了一种便捷的方式。CAS理论是它实现整个java包的基石。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)
CAS虽然很高效地解决了原子操作,但是CAS仍然存在三个问题
1)ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2)循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3)只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
这个时候就可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
AtomicStampedReference解决CAS机制中ABA问题Demo
只有版本号相同才可以进行cas修改
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Test {
/**
**/
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger(0);
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(100,1000);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
integer.compareAndSet(0,1);
integer.compareAndSet(1,0);
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = integer.compareAndSet(0, 1);
System.out.println("AtomicInteger替换");
if(b) System.out.println("0已经被替换为1");
else System.out.println("替换失败");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
reference.compareAndSet(100,-100,
reference.getStamp(), reference.getStamp()+1);
reference.compareAndSet(-100,100,
reference.getStamp(), reference.getStamp()+1);
}
});
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
int stamp = reference.getStamp();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = reference.compareAndSet(100, -100,
stamp, reference.getStamp() + 1);
System.out.println("AtomicStampedReference替换");
if(b) System.out.println("100已经被替换为-100");
else System.out.println("替换失败");
}
});
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出结果:
AtomicInteger替换
0已经被替换为1
AtomicStampedReference替换
替换失败