synchronized¶
三种应用方式¶
synchronized 关键字最主要有以下 3 种应用方式,下面分别介绍
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
synchronized 的优化¶
锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。锁的升级是单向的。
偏向锁¶
如果一个线程获得了锁,那么锁就进入偏向模式,此时 Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁。对于没有锁竞争的场合,偏向锁有很好的优化效果。
锁升级过程¶
- 当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的 threadID,因为偏向锁不会主动释放锁
- 因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致
- 如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;
- 如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活
- 如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;
- 如果存活,那么立刻查找该线程(线程1)的栈帧信息
- 如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁
- 如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
轻量级锁¶
轻量级锁适合的场景是线程交替执行同步块的场合。竞争锁对象的线程不多,而且线程持有锁的时间也不长。自旋这等待锁释放。
锁升级过程¶
- 线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址
- 如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁
- 如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
锁粗化¶
将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。
锁消除¶
JVM在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。
JAVA对象头¶
synchronized 使用的锁对象是存储在 Java 对象头里。
在线程执行同步代码块之前,JVM会现在当前线程的栈桢中创建用于存储锁记录的空间,并将锁对象头中的 MarkWord 信息复制到锁记录中( Displaced Mard Word)。然后线程尝试使用 CAS 将对象头中的 MarkWord 替换为指向锁记录的指针。

monitor¶
synchronized 通过 monitorenter 和 monitorexit 实现。
monitor竞争¶
- 通过CAS尝试把monitor的
_owner字段设置为当前线程 - 如果设置之前的
_owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行_recursions ++,记录重入的次数 - 若之前
_owner指向的BasicLock在当前线程栈上,说明当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程,该线程成功获得锁并返回 - 如果获取锁失败,则等待锁的释放
monitor等待¶
- 当前线程被封装成ObjectWaiter对象node
- 在循环中,通过CAS把node节点添加到链表
- 通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当前线程挂起,等待被唤醒
ObjectMonitor¶
进入wait/notify方法之前,要获取synchronized锁。
ObjectMonitor对象中有两个队列:_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表;_owner指向获得ObjectMonitor对象的线程。
- _WaitSet :处于wait状态的线程
- _EntryList:处于等待锁block状态的线程

object.wait()¶
- 将当前线程封装成ObjectWaiter对象node
- 将node添加到_WaitSet列表
- 释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象
- 最后底层的park方法会挂起线程
object.notify()¶
- 如果当前_WaitSet为空,即没有正在等待的线程,则直接返回
- 获取_WaitSet列表中的第一个ObjectWaiter节点
- 根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或自旋
流程¶
- 多个线程monitorenter时,只有一个可以获取lock对象关联的monitor,和monitor建立关联。
- 获取失败的线程加锁失败,进入等待队列。
- 获得monitor的线程执行wai时,将当前线程放入wait set,等待被唤醒,并放弃lock对象上的所有同步声明。
- notify方法会选择wait set中任意一个线程唤醒,notifyAll方法会唤醒monitor的wait set中所有线程。