Java并发编程03:锁的种类与实现
重量级锁的实现
重量级锁
是锁最重的一种实现方式,提供了最完整的同步功能.
锁对象头中的Monitor
对象: 记录锁信息
Java对象的锁信息记录在锁对象头中的Monitor
对象,Moniter
对象中持有三个引用:
- 等待队列
_WaitList
: 存储处于等待状态的线程的引用(即被锁的wait()
方法阻塞的线程). - 就绪队列
_EntryList
: 存储处于就绪状态的线程的引用. - 持有锁线程标记
_Owner
: 存储当前持有锁线程的引用
多线程执行时要与锁对象头中的Monitor
对象进行如下交互:
- 当多线程并发访问同一个同步代码块时,这些线程首先会进入
_EntryList
. - 当线程抢到锁标记开始执行同步代码块时,
Monitor
中的计数器进行自增+1操作
,并将_Owner
引用指向当前线程. - 当线程执行完同步代码块释放锁时,
Monitor
中的计数器进行自减-1操作
,若计数器计数至0
,则将_Owner
指向null
(释放锁). - 若正在执行的线程调用了锁的等待
wait()
方法,则Monitor
中的计数器进行清零操作
,将当前线程移入_WaitList
队列且将_Owner
指向null
(释放锁). - 若正在等待的线程被唤醒,则将被唤醒的线程移出
_WaitList
队列并将其放入_EntryList
队列.
理解锁的可重入性
在Java中,同步锁是可重入的,只有同一线程调用同步方法或执行同步代码块,对同一个对象加锁时才可以重入.
当线程每次获得锁时,会在Monitor
中的计数器进行自增运算,若当前线程连续多次获得同一把锁,会在Monitor
中的计数器进行多次递增. 当同步代码块执行结束后,Monitor
中的计数器会进行自减运算,直到所有同步代码都执行结束,Monitor
中的计数器减至0
时,才会释放锁标记(将_Owner
置为null
)
锁的类型
Java中锁的实现可以分为偏向锁
,自旋锁
,轻量级锁
,重量级锁
.锁的重量依次增加.
-
偏向锁
偏向锁
是一种编译解释锁.若代码的设计使得不可能出现多线程并发争抢同一把锁时,JVM会在编译时自动放弃锁的同步信息,而是通过Monitor
的ACC_SYNCHRONIZED
标记获得锁的线程.这可以避免锁的争抢和锁池状态的维护,提高效率. -
轻量级锁
当偏向锁
的条件不满足,亦即的确有多线程并发争抢同一锁对象时,但并发数不大时,优先使用轻量级锁
.放弃锁的同步信息,而是通过Monitor
的ACC_SYNCHRONIZED
标记获得锁的线程,ACC_UNSYNCHRONIZED
标记未获得锁的线程. 一般只有两个线程争抢锁标记时,优先使用轻量级锁
. -
自旋锁
自旋锁
是一个过渡锁,是从偏向锁
到轻量级锁
的过渡.
若当前线程未能抢到锁,为提高效率,JVM自动执行若干次空循环,再次申请锁,而不是直接进入阻塞状态. -
重量级锁
重量级锁
即为我们在上一节探讨的具有完整Monitor
功能的锁.
Java中的各种锁对程序员来说是透明的: 在创建锁时,JVM先创建最轻的锁,若不满足条件则将锁逐次升级. 这四种锁之间只能升级,不能降级.