java - CAS底层原理及与synchronized的对比

2021-04-12 17:25:22 浏览数 (1)

底层原理疑问

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是采用悲观互斥锁,即使没有线程竞争也会加上monitorentermonitorexit指令(不考虑jdk1.6之后的锁优化),会有线程的阻塞行为,影响性能。

4. 场景选择

CAS - 竞争小的情况,竞争过多造成自旋过多,造成cpu空跑 synchronized - 竞争大的情况,竞争过小加悲观锁比较重

参考

  1. 一文彻底搞懂CAS实现原理

0 人点赞