主要资料参考于 JavaGuide https://github.com/Snailclimb/JavaGuide#java,再加上自己的理解
JDK 1.6 之后对锁的实现进行了大量的优化,比如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等。
1. synchronized 主要的使用方法
- 修饰实例方法:给当前对象加锁,也就是
this
- 修饰静态方法:给当前的类加锁,也就是给这个类的
class
对象加锁,基本上相当于这个class
的全局锁了 - 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块之前要获得给定对象的锁
2. synchronized 的底层实现
synchronized
同步代码块实现使用的是monitorenter
和monitorexit
指令,也就是分别指向同步代码块开始和结束的位置。- 但是在修饰方法的时候使用的是
ACC_SYNCHRONIZED
标识,JVM 通过此标识来判断此方法为同步方法。 - 使用
monitor
的时候是通过监控对象的 对象头(markword
)中的相关属性来判断锁的状态。
- 再底层就是通过汇编指令了,intel 是
lock comxchg
3. 锁的升级过程
Lock Record
- 在
monitorenter
的时候,如果对象没有被锁定01
,虚拟机会首先在当前的栈中创建Lock Record
的空间,用于存储锁对象的Mark Word
的拷贝,这个过程叫做Displaced Mark Word
。 Lock Record
是线程私有的数据结构,每一个线程都有一个可用的 Lock Record 列表,同时还有一个全局的可用列表。每一个被锁住的对象Mark Word
都会与一个Lock Record
关联。
Lock Record | 描述 |
---|---|
Owner | 当线程成功拥有改锁后保存线程的唯一标识,其它情况下为 NULL |
EntryQ | 关联一个系统互斥锁 semaphore ,阻塞所有试图锁住 record 失败的线程 |
RcThis | 表示 blocked 或 waiting 在改 record 上的所有线程的个数 |
Nest | 用来实现重入锁的计数 |
HashCode | 从对象头拷贝过来的 HashCode,可能还包含分代年龄 |
Candidate | 用来避免不必要的阻塞或者等待线程唤醒,值为 0 或 1 ,0 表示没有需要唤醒的线程,1 表示需要唤醒一个继任线程来竞争锁 |
锁升级
- 进入
monitorenter
检查Lock Record
做Displaced Mark Word
- 没有偏向锁的话,升级为偏向锁,偏向锁偏向第一个线程
- 偏向锁被争用的时候升级为轻量级锁,
Displaced Mark Word
,CAS 替换Mark word
的Lock Record
的指针(线程争用)- 当线程竞争特别激烈的时候,关闭偏向锁效率反而更高
- 默认是开启偏向锁的,
-XX:-UseBiasedLocking
- 轻量级自旋超过 10,升级为重量级锁,生成或者服用
monitor
对象,mark wrod
指向monitor
,线程阻塞进入_EntryList
- 任何一个线程超过 10,或者 CPU 核心数的一半
- 默认值为 10,可以通过
-XX:+PreBlockSpin=10
来指定次数
4. 其和 ReentrantLock 的区别
- 都是可重入锁,一个线程获得了某个对象的锁,锁还没释放,是可以在此获取对象的锁。同一个线程获取锁,计数器
+1
,计数器为0
才能释放锁。 - sychronized 依赖于 JVM,ReentrantLock 依赖于 Java 的 API,还需要使用
try
finally
来进行lock()
、unlock()
ReentrantLock
相对于 sychronized 增加了三个高级功能:等待可中断、可实现公平锁、可实现选择性通知性
高级功能
- 等待可中断:中断等待锁的线程,
lock.lockInterruptibly()
,中断等待。 - 公平锁:可实现公平锁(使用队列的 FIFO 实现)和非公平锁,可以通过
ReentrantLock(boolean fair)
构造方法来指定是否公平。公平锁就是先等待的线程先获得。 - 选择性通知:
sychronized
与wait()
和notify()
/notifyAll()
来实现等待通知机制。ReentrantLock
类也可以实现,需要借助Condition
接口与newCondition()
方法,它可以实现多路通知也就是一个 Lock 对象创建多个Condition
实例,线程对象可以注册在Condition
中,从而可以又选择性的进行线程通知,更灵活。Condition
实例的signalAll()
只会唤醒在该Condition
实例中的所有等待线程