Java并发2——CAS
Posted 我永远信仰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发2——CAS相关的知识,希望对你有一定的参考价值。
CAS、乐观锁、悲观锁
线程同步操作资源对象
多个线程想要操作一个资源对象。如何实现同步?
第一反应是使用互斥锁、但是互斥锁同步方式是悲观的。
悲观锁
悲观锁:操作系统认为,如果不严格同步线程调用,那么一定会产生异常。
缺点:如果大部分线程只是对资源进行读操作,悲观锁在每次被调用都是对资源经行锁定,效率无疑是极低的。
或者在同步代码块执行的时间远远小于线程切换的时间,使用悲观锁也是非常不好的。
那么如何实现:不锁定资源,也能线程同步?
那就是CAS:(Compare and swap)比较并且交换。
乐观锁(CAS)
比如两个线程争一个资源对象,资源对象中有一个状态值,0表示空闲没有被线程使用,那么这时候线程可以去获取该资源对象。
当线程拿到该资源对象,会生成两个值oldValue=0,newValue=1;oldValue表示拿到资源对象时候资源对象的值,这是作用于compare;newValue表示线程将其修改的值。
假设此时A、B两个线程都抢到了资源对象,想修改它的状态值为1,并占用它。如果A线程执行的比较好。先将资源对象的状态值swap操作修改成1,那么当B线程修改的时候发现oldValue不等于资源对象的状态值,因为其被A修改了,所以放弃swap操作。
不过B也不是直接放弃,而是进行自旋等待A的释放,不断的对资源对象经行compare,通常会配置自旋次数来防止死循环,因为自选是cpu空转,占用一定的开销,特别是自选线程很多的情况,这个后面总结。
但这个情况下,需要保证cas是原子性的。否则出现ABA问题。
那么应该如何实现cas的原子性呢?通过锁来实现?那前面的功夫岂不是白搭了
这个不用我们担心,CPU已经原生的支持了cas,上层经行调用即可。
底层的cmpxchg,留个坑,底层是如何实现无锁同步的
CAS这种同步机制,被称为乐观锁。CAS没有用到锁,是一种无锁的同步机制。
乐观锁不用上锁,而是cpu将这个过程变成是字节码指令级别的,拥有原子性。如果乐观锁再加锁,就违背了无锁的初衷
Java中的例子
启动三条线程,将一个值累加到1000.
public class Test01 {
static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while (num.get() < 1000) {
System.out.println(Thread.currentThread().getName() + ":" + num.incrementAndGet());
}
}
}).start();
}
}
}
AtomicInteger
的底层是如何通过CAS做到无锁同步
查看AtomicInteger
源码
看我们调用的num.incrementAndGet
()方法,确实是这样
查看getAndAddInt
,这里调用了CAS,而且这里出现了一个循环,也就是之前说到的自旋。循环次数是可以通过启动参数来配置,如果不配置,默认次数是10,并不会出现死循环。
CAS是一个本地方法,和调用的平台相关
ABA
[CAS是什么?ABA问题又应该如何理解? - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/139635112#:~:text=ABA 问题的过程是当有两个线程 T1 和 T2 从内存中获取到值A,线程 T2 通过某些操作把内存,值修改为B,然后又经过某些操作将值修改为回值A,T2退出。 线程 T1 进行操作的时候 ,使用预期值同内存中的值比较,此时均为A,修改成功退出。 但是此时的A以及不是原先的A了,这就是 ABA 问题)
以上是关于Java并发2——CAS的主要内容,如果未能解决你的问题,请参考以下文章