底层原理疑问
CAS是比较并交换,AtomicInteger
最终都是调用Unsafe.compareAndSwapInt
方法进行实现,那Unsafe.compareAndSwapInt
为什么是原子性的呢?它是怎么实现的?它的同步也是依赖于互斥吗?他与synchronized
锁的底层实现有什么不同吗? 这两种同步方式的场景选择?
解答
1. Unsafe.compareAndSwapInt
为什么是原子性的?
他的原子性是由硬件指令实现的,底层硬件通过将 CAS 里的多个操作在硬件层面语义实现上,通过一条处理器指令保证了原子性操作。这些指令如下所示:
(1)测试并设置(Tetst-and-Set)
(2)获取并增加(Fetch-and-Increment)
(3)交换(Swap)
(4)比较并交换(Compare-and-Swap)
(5)加载链接/条件存储(Load-Linked/Store-Conditional)
前面三条大部分处理器已经实现,后面的两条是现代处理器当中新增加的。而且根据不同的体系结构,指令存在着明显差异。
在IA64,x86 指令集中有cmpxchg
指令完成 CAS 功能,在 sparc-TSO 也有 casa 指令实现,而在 ARM 和 PowerPC 架构下,则需要使用一对 ldrex/strex 指令来完成 LL/SC 的功能。在精简指令集的体系架构中,则通常是靠一对儿指令,如:load and reserve 和 store conditional 实现的,在大多数处理器上 CAS 都是个非常轻量级的操作,这也是其优势所在。
2. 怎么实现的
它采用了缓存锁定
现在都是多核 CPU 处理器,每个 CPU 处理器内维护了一块字节的内存,每个内核内部维护着一块字节的缓存,当多线程并发读写时,就会出现缓存数据不一致的情况。
此时,处理器提供:
-
总线锁定
当一个处理器要操作共享变量时,在 BUS 总线上发出一个 Lock 信号,其他处理就无法操作这个共享变量了。
缺点很明显,总线锁定在阻塞其它处理器获取该共享变量的操作请求时,也可能会导致大量阻塞,从而增加系统的性能开销。 -
缓存锁定
后来的处理器都提供了缓存锁定机制,也就说当某个处理器对缓存中的共享变量进行了操作,其他处理器会有个嗅探机制,将其他处理器的该共享变量的缓存失效,待其他线程读取时会重新从主内存中读取最新的数据,基于 MESI 缓存一致性协议来实现的。
现代的处理器基本都支持和使用的缓存锁定机制。
3. 它的同步也是依赖于互斥吗?他与synchronized
锁的底层实现有什么不同吗?
它与synchronized
最大的不同就是,CAS
采用的缓存锁定,在没有竞争的时候没有额外的操作,当有竞争了才会有通知缓存失效机制。而synchronized
是采用悲观互斥锁
,即使没有线程竞争也会加上monitorenter
和monitorexit
指令(不考虑jdk1.6之后的锁优化),会有线程的阻塞行为,影响性能。
4. 场景选择
CAS - 竞争小的情况,竞争过多造成自旋过多,造成cpu空跑
synchronized - 竞争大的情况,竞争过小加悲观锁比较重
网友评论