详解java 中的CAS与ABA

网友投稿 218 2023-01-16

详解java 中的CAS与ABA

1. 独占锁:

属于悲观锁,有共享资源,需要加锁时,会以独占锁的方式导致其它需要获取锁才能执行的线程挂起,等待持有锁的钱程释放锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。java中synchronized和ReentrantLock等独占锁就是悲观锁的思想。

1.1 乐观锁的操作

多线程并发修改一个值时的实现:

public class SimulatedCAS {

//加volatile的目的是利用其happens-before原则,保证线程可见性

private volatile int value;

public synchronized int getValue() { return value; }

public synchronized int compareAndSwap(int expectedValue, int newValue) {

int oldValue = value;

if (value == expectedValue)

value = newValue;

return oldValue;

}

}

2. 乐观锁:

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。 在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。乐观锁一般会使用版本号机制或CAS算法实现。

2.1 CAS操作

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。

类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。 CAS实现计数器的操作:

public class CasCounter {

private SimulatedCAS value;

public int getValue() {

yfZaSlpyFreturn value.getValue();

}

public int increment() {

int oldValue = value.getValue();

while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue)

oldValue = value.getValue();

return oldValue + 1;

}

}

3. 原子变量类

JDK5.0之后加入了java.util.concurrent.atomic 包,其中的AtomicInteger; AtomicLong; AtomicReference; AtomicBoolean 等都是在CAS基础上实现的。

4. CAS的缺陷

循环时间太长,如果自旋长时间不成功,会给cpu带来极大的开销,有兴趣的可以使用JMH测试下AtomicLong 和 LongAdder的性能。

ABA问题: CAS需要检查待操作值有没有发生改变,如果没有发生改变则更新。 但是存在这样一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的ABA问题。 在运用CAS做Lock-Free操作中有一个经典的ABA问题:比如线程1从内存位置V中取出A,这时另一个线程2也从内存中取出A,并且线程2进行了操作之后变成了B,然线程2又将V位置数据变成了A,这时候线程1进行CAS操作发现内存中仍然是A,然后线程1 操作成功。看上去是成功了,实际上有隐藏的问题: 现有一个用单向链表实现的FIFO堆栈,栈顶为A,这时线程1已经知道A.next为B,然后希望用CAS将栈顶替换为B,在线程1执行上面这条指令之前,线程2 介入,将A、B出栈,再push D、C、A,此时A位于栈顶,B已经不在栈中;此时线程1执行CAS,发现栈顶仍为A,所以CAS成功,即将栈顶变成B,但实际上此时B与 当前栈中元素D、C没有关系,B.next为null,这样一来就直接把C、D丢掉了。 对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A,变成A(1) —> B(2) —> A(3)。 java中AtomicStampedReference也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题。

public class AtomicTest {

private static AtomicInteger atomicInteger = new AtomicInteger(100);

private static AtomicStampedReference atomicStampedReference =

newhttp:// AtomicStampedReference(99, 0);

public static void main(String[] args) throws InterruptedException {

Thread thread1 = new Thread(() -> {

atomicInteger.compareAndSet(99, 100);

atomicInteger.compareAndSet(100, 99);

});

Thread thread2 = new Thread(() -> {

try {

TimeUnit.SECONDS.sleep(1);

}catch (InterruptedException e){

e.printStackTrace();

}

boolean b = atomicInteger.compareAndSet(99, 100);

System.out.println(b);

});

thread1.start();

thread2.start();

thread1.join();

thread2.join();

Thread refT1 = new Thread(() -> {

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

atomicStampedReference.compareAndSet(99yfZaSlpyF, 100,

atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

atomicStampedReference.compareAndSet(100, 99,

atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

});

Thread refT2 = new Thread(() -> {

int stamp = atomicStampedReference.getStamp();

System.out.println("before sleep : stamp = " + stamp); // stamp = 0

try {

TimeUnit.SECONDS.sleep(2);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("after sleep : stamp = " + atomicStampedReference.getStamp());//stamp = 1

boolean c3 = atomicStampedReference.compareAndSet(99, 100, stamp, stamp+1);

System.out.println(c3); //false

});

refT1.start();

refT2.start();

}

}

结果如下:

true

before sleep : stamp = 0

after sleep : stamp = 2

false

也就是说AtomicInteger更新成功,而AtomicStampedReference更新失败。

以上就是详解java 中的CAS与ABA的详细内容,更多关于java 中的CAS与ABA的资料请关注我们其它相关文章!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:中汽物流查询(中汽运输查询)
下一篇:JVM中ClassLoader类加载器的深入理解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~