synchronized的执行过程
- 线程抢锁时,JVM首先检测内置锁对象Mark Word中biased_lock(偏向锁标识)是否设置 成1,lock(锁标志位)是否为01,如果都满足,确认内置锁对象为可偏向状态。
- 在内置锁对象确认为可偏向状态之后,JVM检查Mark Word中线程ID是否为抢锁线程ID, 如果是,就表示抢锁线程处于偏向锁状态,抢锁线程快速获得锁,开始执行临界区代码。
- 如果Mark Word中线程ID并未指向抢锁线程,就通过CAS操作竞争锁。如果竞争成功,就 将Mark Word中线程ID设置为抢锁线程,偏向标志位设置为1,锁标志位设置为01,然后执行临界 区代码,此时内置锁对象处于偏向锁状态。
- 如果CAS操作竞争失败,就说明发生了竞争,撤销偏向锁,进而升级为轻量级锁。
- JVM使用CAS将锁对象的Mark Word替换为抢锁线程的锁记录指针,如果成功,抢锁线程 就获得锁。如果替换失败,就表示其他线程竞争锁,JVM尝试使用CAS自旋替换抢锁线程的锁记录 指针,如果自旋成功(抢锁成功),那么锁对象依然处于轻量级锁状态。
- 如果JVM的CAS替换锁记录指针自旋失败,轻量级锁膨胀为重量级锁,后面等待锁的线程 也要进入阻塞状态。
CAS
优势
- 属于无锁编程,线程不存在阻塞和唤醒这些重量级的操作。
- 进程不存在用户态和内核态之间的运行切换,进程不需要承担频繁切换的开销。
CAS 操作的弊端和规避措施
CAS 操作的弊端 CAS操作的弊端主要有以下4点。
(1)ABA问题
使用CAS操作内存数据时,当数据发生过变化也能更新成功,如操作序列A==>B==>A时,最 后一个CAS的预期数据A实际已经发生过更改,但也能更新成功,这就产生了ABA问题。
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候将版 本号加1,那么操作序列A==>B==>A的就会变成A1==>B2==>A3,如果将A1当作A3的预期数据, 就会操作失败。
JDK提供了两个类AtomicStampedReference和AtomicMarkableReference来解决ABA问题。比较 常用的是AtomicStampedReference类,该类的compareAndSet方法的作用是首先检查当前引用是否等 于预期引用,以及当前印戳是否等于预期印戳,如果全部相等,就以原子方式将引用和印戳的值一 同设置为新的值。
(2)只能保证一个共享变量之间的原子性操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个 共享变量操作时,CAS无法保证操作的原子性。
一个比较简单的规避方法为:把多个共享变量合并成一个共享变量来操作。
JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个 AtomicReference实例后再进行CAS操作。比如有两个共享变量i=1、j=2,可以将二者合并成一个 对象,然后用CAS来操作该合并对象的AtomicReference引用。
(3)无效CAS会带来开销问题
自旋CAS如果长时间不成功(不成功就一直循环执行,直到成功为止),就会给CPU带来非 常大的执行开销。
(4)在部分CPU平台上存在“总线风暴”问题
CAS操作和volatile一样也需要CPU进行通过MESI协议各个内核的“Cache一致性”,会通过 CPU的BUS(总线)发送大量MESI协议相关的消息,产生“Cache一致性流量”。因为总线被设计 为固定的“通信能力”,如果Cache一致性流量过大,总线将成为瓶颈,这就是所谓的“总线风暴”。
提升CAS性能
提升CAS性能有效方式之一是以空间换时间,分散竞争热点。较为常见的方案为:
1)分散操作热点,使用LongAdder替代基础原子类AtomicLong,LongAdder将单个CAS热点 (value值)分散到一个cells数组中。
2)使用队列削峰,将发生CAS争用的线程加入一个队列中排队,降低CAS争用的激烈程度。 JUC中非常重要的基础类AQS(抽象队列同步器)就是这么做的。
提升CAS性能有效方式之二是使用线程本地变量,从根本上避免竞争。
volatile
Java内存模型的规定如下: 1)所有变量存储在主存中。 2)每个线程都有自己的工作内存,且对变量的操作都是在工作内存中进行的。 3)不同线程之间无法直接访问彼此工作内存中的变量,要想访问只能通过主存来传递。
volatile关键字的主要有两层语义:
- 不同线程对volatile变量的值具有内存可见性,即一个线程修改了某个volatile变量的值,该值对其他线程立即可见。
- 禁止进行指令重排序。用volatile修饰的变量在硬件层面上会通过在指令前后加入内存屏障来实现。
volatile能保证数据的可见性,但volatile不能完全保证数据的原子性,对于volatile类型的变量进行复合操作(如++)其仍存在线程不安全的问题。
AQS
AQS建立在CAS原子操作和volatile可见性变量的基础之上,为上层的显式锁、同步工具类、 阻塞队列、线程池、并发容器、Future异步工具提供线程之间同步的基础设施。所以,AQS在JUC 框架中的使用是非常广泛的。
ReentrantLock
- 非公平同步器ReentrantLock.NonfairSync的核心思想就是当前进程尝试获取锁的时候,如果发 现锁的状态位是0,就直接尝试将锁拿过来,然后执行setExclusiveOwnerThread(),根本不管同步队 列中的排队节点。
- 公平同步器ReentrantLock.FairSync的核心思想是通过AQS模板方法去进行队列入队操作。