一、synchronized 锁
sychronized 加锁是一个自适应的过程,减少了程序员使用时的负担。会根据情况依次进行锁升级,无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁。
1. 偏向锁
一个例子:
A和B是情侣 A答应B不结婚也会一直在一起------>这就是偏向锁,避免了高成本的结婚操作(加锁)。而有一天 一个帅哥出现在了A的身边 ,这时候就有必要结婚(加锁)。
- 并不是真正的加锁,只是一个名分。
- 偏向锁不是真的“加锁”,只是给对象头中做一个“偏向锁的标记”,记录这个锁属于哪个线程。
- 偏向锁本质上属于“延迟加锁”,也就是说 能不加锁就不会加锁,从而来避免不必要的加锁开销。
2. 轻量级锁(自适应的自旋锁)
倘若偏向锁不够用,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。
- 轻量级锁通过CAS来实现的,通过CAS检查并更新一块内存(比如null => 该线程的使用),如果更新成功,则认为加锁成功。如果更新失败,则认为锁被占用,一直自旋式的等待。
- 轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。
- 轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
3. 重量级锁
如果竞争进一步激烈,自旋锁也不够用,不能快速获取到锁的状态,就会膨胀为重量级锁。这里的重量级锁就是指 内核提供的 mutex
- 执行加锁操作,先进入内核态
- 在内核态判定当前锁是否已经被占用
- 如果锁没有被占用,则说明加锁成功,并切回到用户态
- 如果被占用了,加锁失败,此时进入锁的等待队列,挂起,等待被操作系统唤醒
- 经历了一些沧海桑田,这个锁就被释放后,操作系统也想起了这个挂起的线程,于是唤醒这个线程,并且尝试重新获取锁
二、ReentrantLock
1. ReentrantLock 相关介绍
属于 JUC(java.util.concurrent ) 中常见的类
是一个可重入互斥锁。 功能和 synchronized 一样 都是用来实现互斥效果,保证线程安全。
2. ReentrantLock 的用法:
- lock () :加锁,如果获取不到锁就 死等
- trylock ( 超时时间 ) :尝试加锁加锁,能加酒驾,不能加就不加。可以设置等待时间
- unlock() :解锁
- 可以实现公平锁:ReentranLock 默认是不公平的,如果在构造的时候传入一个 true 参数就编程公平锁了。( ReentrantLock locker = new ReentrantLock(true) )
三、ReentrantLock 和 synchronized 的区别
- synchronized 是一个关键字,JVM内部实现的。ReentrantLock 是一个标准库中的类,在JVM外使用 java 代码实现。
- synchronized 使用的时候不需要手动释放锁,ReentrantLock 使用需要手动释放锁。
- synchronized 申请锁失败会一直等带别的线程释放锁,ReentrantLock 可以通过trylock 方法等待一段时间就放弃申请锁。
- synchronized 是非公平锁,ReentrantLock 默认是非公平锁,但可以构造的时候传一个 true 开启公平锁模式。
- synchronized 是通过Object的 wait 、notify 实现 等待、唤醒,每次唤醒的是一个随机的进程。ReentrantLock 搭配 Condition 类 实现 等待、唤醒,可以更加精确的唤醒某个指定的线程。
四、我们使用的时候如何选择呢?
- 锁竞争不激烈的时候,使用 synchronized,效率更高,自动释放更方便
- 锁竞争激烈的时候,使用 ReentrantLock,搭配 trylock 更灵活控制加锁的行为,不会死等
- 如果需要公平锁,使用 ReentrantLock