前言
公司业务渐渐要写多线程模块了,因为自己对多线程这块并不是非常了解,所以在bilibili上学习马士兵老师的高并发课程,以下也是听完第一节课后做的笔记。
CAS问题引入
在多线程环境下当我们使用AtmoicInteger中getAndIncrement()
方法的当时候发现我们不需要加锁。他的源码其实时应用了CAS操作,这是一种无锁,也有人称自旋锁,也是乐观锁。
CAS图解
1.当一个线程进来拿到了当前值E(比如当前E是0,并将其存为N)。
2.开始计算,计算结果V(计算后当结果V为1)
3.计算完成后,比较N和E当值是否相等,如果相等就直接把E(0)值更新为V(1)。
如果不相等,就说明,E被其他线程改动过,如果改动之后为1,也就是当前E被改为1了,则重复上述动作,拿到当前值E(为1),计算结果为V(2),继续比较N(1)和E….
CAS存在的ABA问题
CAS存在ABA的问题,即一个线程A进来拿到当前值E(0),但是在修改过程中,同时两个线程进来对E进行了-1和+1的操作,由此当线程A要去比较原始值E的时候发现和当时读到的值是相同的,实际上这个E已经被两个线程修改过了,只不过修改过后的结果和最开始的E是一样的。由此引发了ABA的问题。
ABA问题的解决,通过加版本号,每次修改值E都需要改变其版本号,当比较原始值是否相同的时候同时也要比较版本号是否相同。版本号可以是多种形式(boolean、时间戳等)。
CAS问题AtomicInteger源码跟踪
在AtomicInteger中内部是由一个int域来保存值的,其由volatile关键字修饰,用于保证可见性。
1 | private volatile int value; |
其实不只是Atomic类中使用了compareAndSwap
方法,像synchronized
,volatile
底层也是这么实现的,这个等之后在看,现在我们来看看AtmoicInteger的getAndIncrement()
的源码
1 | /** |
如果跟到JDK底层我们会发现他是Unsafe
类中的一个方法
1 | public final int getAndAddInt(Object var1, long var2, int var4) { |
再往下面跟,我们发现一个native修饰的方法,这就意味着这已经不是java代码实现了,是C++或C实现的,再继续跟就要跟到HotSpot
也就是JVM的源码中去了。
1 | public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); |
我们继续跟踪到Hotspot中到unsafe.cpp
,中的实现源码如下
1 | UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) |
然后继续跟踪Atomic::cmpxchg(x, addr, e)
到atomic_linux_x86.inline.hpp
,由于这边是linux系统所以由linux自己的实现,不同的操作系统实现不同。其中的汇编源码
1 | inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { |
asm是汇编语言,直接和硬件打交道,我们发现cmpxchg
语句硬件直接支持。
LOCK_IF_MP(%4)
其中的MP指的是Machine Processprs如果一个CPU是需要用cmpxchg
就可以了,但如果多个CPU还要加前面的LOCK指令。
最终的实现:是lock cmpxchg
指令
硬件:lock指令在执行后面指令的时候锁定一个北桥信号(不采用锁总线的方式)。
LOCK会锁定这块内存区域到缓存行(缓存行锁定)并写回主内存。
inter64位开发手册对lock指令对解释:
1.会将当前处理器缓存行对数据立即写回到系统内存。
2.这个写回内存到操作会引起在其他CPU缓存了该内存地址到数据无效(MESI协议)
3.提供内存屏障功能,使得lock前后指令不能重排序ß
CAS是否真的是原子性
问题:如果在比较的时候,已经比较好了之后,但还没修改值之前,被其他线程修改了,那么其他线程的值会被当前值覆盖。
如果底层单单是一个cmpxchg
指令,有多个CPU,他是不具有原子性的。但是在多个CPU的情况下,LOCK
指令起到了关键性的作用,即一个CPU对一个值进行修改的时候,不允许其他CPU修改这个值。也是因为这个lock给cmpxchg提供了原子性。
如果有小伙伴,想要一起交流学习的,欢迎添加博主微信。