ReentrantLock可重入锁—源码详解
2021-12-23 10:38
155 查看
开始这篇博客之前,博主默认大家都是看过AQS源码的~什么居然没看过🤬猛戳下方👇👇👇 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(二)资源的获取和释放 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(三)条件变量
介绍
ReentrantLock是可重入锁,是JUC提供的一种最常用的锁。“可重入”的意思就是:同一个线程可以无条件地反复获得已经持有的锁
ReentrantLock有公平锁和非公平锁两种模式,底层使用的正是
AbstractQueuedSynchronizer这个伟大的并发工具
ReentrantLock的结构如下图所示:
ReentrantLock实现了
Lock接口,该接口定义了一个锁应该具备的基本功能,即加锁、解锁、创建条件变量等功能。源码如下:
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
使用
ReentrantLock,主要使用
lock和
unlock方法,也会用到
newCondition来创建条件变量,实现一些条件同步功能
回到上面的结构图,可以看到,
ReentrantLock的功能主要是借助其内部类
Sync来实现,而
Sync类是继承了
AbstractQueuedSynchronizer,并衍生出两个子类
FairSync、
NonfairSync,分别对应公平锁和非公平锁两种模式。实际应用中,一般非公平锁的效率要高于公平锁。具体原因见最后一节“相关面试题" 作者:酒冽 出处:https://www.cnblogs.com/frankiedyz/p/15719681.html
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
Sync
ReentrantLock中的
sync域是是一个
Sync类对象,
ReentrantLock使用
sync来实现主要的功能。
Sync类是
ReentrantLock的内部类:
/** Synchronizer providing all implementation mechanics */ private final Sync sync;
Sync类继承了AQS,使用AQS的
state作为锁的重入数,其源码如下:
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; // 用于辅助ReentrantLock执行Lock接口的lock方法,所以定义了一个同名lock方法 abstract void lock(); // 执行非公平的tryLock,是非公平锁子类NonFairSync实现的tryAcquire方法的主要逻辑,也是ReentrantLock的tryLock的主要逻辑? final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } // 为了支持使用条件变量,Sync实现了AQS中的isHeldExclusively,并提供了newCondition方法创建条件变量 protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } final ConditionObject newCondition() { return new ConditionObject(); } // ReentrantLock的三个同名方法,都委托给了下面这三个方法,用于获取锁的一些信息 final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } final boolean isLocked() { return getState() != 0; } /** * Reconstitutes the instance from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } }
总结一下,
Sync类有以下几个作用:
- 定义抽象
lock
方法:Sync
定义了lock
这一个抽象方法,强迫两个子类去实现,而ReentrantLock
的lock
方法就可以直接委托给Sync
的lock
方法去执行 - 非公平模式的尝试获取锁:
Sync
提供了nonfairTryAcquire
方法,提供非公平尝试获取锁的方法。不仅非公平子类NonfairSync
实现tryAcquire
方法需要委托给nonfairTryAcquire
来处理,而且ReentrantLock
中的tryLock
方法也会委托给它来处理 - 尝试释放锁:
Sync
实现了AQS中的tryRelease
方法,因为不管是公平模式还是非公平模式,释放锁的逻辑都是相同的,因此在Sync
这一层就提供了具体的实现,而没有下放给子类来实现 - 条件变量的支持:
Sync
实现了AQS中的isHeldExclusively
方法(该方法会被AQS中的ConditionObject
的signal
方法调用),并提供了newCondition
方法创建条件变量 - 获取锁信息的方法:
Sync
提供了getOwner
、getHoldCount
、isLocked
三个方法用于获取锁的信息,外围类ReentrantLock
的三个同名方法会委托这三个方法来执行
获取锁
要利用AQS实现获取锁的功能,需要实现
tryAcquire方法。但是由于公平模式和非公平模式下获取锁的逻辑不同,因此
tryAcquire交给两个子类去实现,
Sync并不实现
但是对于非公平模式的获取锁,
NonFairSync子类实现的
tryAcquire方法实际上委托了
Sync类的
nonfairTryAcquire方法来处理。
nonfairTryAcquire的源码分析放在后面的非公平模式去讲解
释放锁
要利用AQS实现释放锁的功能,需要实现
tryRelease方法。不同于获取锁,对于公平模式和非公平模式来说,释放锁的逻辑是相同的,因此
tryRelease的实现直接交给
Sync这一层来实现,而没有下放给子类来实现
tryRelease是尝试释放资源,而在
ReentrantLock中的语义环境下就是尝试释放锁。其源码如下:
protected final boolean tryRelease(int releases) { int c = getState() - releases; // 如果当前线程不持有锁就去释放锁,会抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果释放锁后发现锁空闲 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; // 返回锁是否空闲,如果空闲则为true }
tryRelease的releases参数说明: 由于每次只释放一个锁,所以调用lock释放锁时tryRelease的releases参数恒为1 但是ReentrantLock支持条件变量,条件变量的await方法也会调用tryRelease方法一次性释放所有的锁资源,此时tryRelease的参数releases不一定为1
AQS中的
release方法会调用
tryRelease方法并接收其返回值,如下:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
如果
tryRelease返回true,说明锁为空闲,那么就需要唤醒等待获取锁而阻塞,且等待最久的线程,让它来获取锁。因此
release会唤醒同步队列的队首线程。如果锁不是空闲,就不需要唤醒任何线程 作者:酒冽 出处:https://www.cnblogs.com/frankiedyz/p/15719681.html
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
非公平锁
非公平锁是借助
Sync和其子类
NonfairSync来实现的。
NonfairSync实现了
Sync定义的
lock抽象方法,以及实现了AQS中的
tryAcquire方法以获取锁。源码如下:
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; // 实现了Sync中定义的lock方法 final void lock() { // 上来就CAS,一点也不客气————非公平性 if (compareAndSetState(0, 1)) // 如果state为0,说明锁是空闲的,直接CAS获取 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 如果锁不空闲,或者CAS竞争失败,就调用acquire去获取1个锁,可能会被阻塞 } // 非公平竞争锁,实际上委托Sync.nonfairTryAcquire来执行 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
lock方法先直接CAS修改
state,如果锁空闲且修改成功,则说明获取到了锁,这里也体现出非公平性,因为它不会谦让已经在同步队列中等待的线程 如果锁非空闲或者竞争失败,则会调用
acquire方法。
acquire会调用非公平锁实现的
tryAcquire方法,再次进行竞争,可能直接获取到锁,也可能再次失败,进入同步队列阻塞等待,这里同样体现了非公平性
非公平锁实现的
tryAcquire实际委托
Sync.nonfairTryAcquire方法来执行,该方法源码如下:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 如果锁空闲,那么直接CAS修改————非公平性 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; // 获取成功 } } else if (current == getExclusiveOwnerThread()) { // 说明是自己持有了锁,可以重入 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); // 直接设置不用CAS return true; } return false; // 获取失败 }
第一个
if体现了该方法的非公平性,获取锁的线程不会给同步队列的队首线程“谦让”,而是直接上去CAS竞争,如果竞争成功,将比队首线程更先获得锁,这体现了不公平性
ReentrantLock默认创建出来的是非公平锁,因为非公平锁的效率一般要高于公平锁:
public ReentrantLock() { sync = new NonfairSync(); }作者:酒冽 出处:https://www.cnblogs.com/frankiedyz/p/15719681.html
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
公平锁
公平锁是借助
Sync和其子类
FairSync来实现的。
FairSync实现了
Sync定义的
lock抽象方法,以及实现了AQS中的
tryAcquire方法以获取锁。源码如下:
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } // 公平竞争锁 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 先检查有无排队等待的线程,如果有就不去CAS竞争——公平性 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 可重入,与非公平是一样的 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
lock方法直接调用
acquire方法获取锁,而
acquire会调用非公平锁实现的
tryAcquire方法,而
tryAcquire也遵循公平性,因此该
lock方法整体上就是公平的
tryAcquire方法会检查锁是否空闲,如果空闲,也不会立即去CAS争夺,而是调用AQS的
hasQueuedPredecessors方法检查是否有线程在同步队列中等待,如果没有才会CAS竞争。如果有就说明不能竞争,返回false
AQS中的
hasQueuedPredecessors方法会检查是否有线程在同步队列中等待,源码如下:
public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; // 如果head等于tail,说明是空队列 // 如果队首的thread域不是当前线程,说明有别的线程先于当前线程等待获取锁 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
要使用公平模式的锁,需要将`ReentrantLock`的构造参数`fair`设为true。如果是false或不设置,则创建的都是非公平模式的锁:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }作者:酒冽 出处:https://www.cnblogs.com/frankiedyz/p/15719681.html
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
Lock接口的实现
ReentrantLock实现了
Lock接口的所有方法,如下:
public void lock() { sync.lock(); } public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } public boolean tryLock() { return sync.nonfairTryAcquire(1); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } public void unlock() { sync.release(1); } public Condition newCondition() { return sync.newCondition(); }
可以看到,
ReentrantLock实现的所有
Lock方法其实都是委托给了
Sync(AQS)来执行 作者:酒冽 出处:https://www.cnblogs.com/frankiedyz/p/15719681.html
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
相关面试题
ReentrantLock是如何实现可重入的
无论是公平锁还是非公平锁,获取锁调用
tryAcquire方法时,获取成功后都会设置当前持有锁的线程是自己。如果再次获取该锁,当发现锁已经被持有时,会判断持有锁的线程是否是自己,如果是就可以不用竞争而直接获取锁
简述非公平锁和公平锁之间的区别
从定义角度来说:
公平锁:获取锁的顺序和请求锁的顺序是一致的,即谁申请得早(等待得久),谁就最先获取锁- 非公平锁:竞争锁时,等待时间最长的线程和刚刚过来竞争锁(不在阻塞同步队列中)的线程都有可能获取锁,CPU时间片轮询到哪个线程,哪个就能获得锁
从源码角度来说: 当锁被占用时,请求锁的所有线程都会按照FIFO的顺序在同步队列中阻塞等待。在锁被释放的时候,如果是非公平锁,则队首线程和刚刚过来请求锁而不在阻塞队列中的线程,都可能获得锁。如果是公平锁,就一定是队首线程获得锁,刚刚过来请求锁得线程会被加入同步队列阻塞等待
从效率上来说:公平锁效率低于非公平锁,主要是两方面的开销
-
代码执行上的开销:公平模式下会多执行一个方法,该方法用于判断是否有其他线程正在同步队列中等待
为什么
ReentrantLock.lock方法不能被其他线程中断
因为
lock方法调用的是AQS中的
acquire方法,该方法忽略中断。而
acquire方法又会调用
acquireQueued方法,该方法执行过程中如果有其他线程中断了当前线程,只会将中断记录下来,不会响应中断。如果锁已经被获取,那么该线程需要被阻塞,阻塞调用的是
LockSupport.unpark方法,该方法接收到中断信号后,不会抛出中断异常,而是返回。返回之后又会进入
acquireQueued的循环,如果不是队首,就重新被阻塞。所以整个过程都不会被其他线程中断,只会将中断记录下来
ReentrantLock与synchronized之间的相同和不同点
相同点: 它们都是通过加锁实现同步,而且都是阻塞式同步,而不是非阻塞式(自旋锁),即当一个线程获取锁后,其他线程再请求锁就会失败而被阻塞,等到锁释放才有机会被唤醒
不同点:
ReentrantLock
:是Java 5之后提供的API层面的互斥锁;需要lock
、unlock
配合try
、finally
使用;支持定时获取锁功能;支持可中断的加锁方法lockInterruptibly
,在等待获取锁时响应中断,会抛出中断异常synchronized
:是Java语言的关键字,通过JVM实现;使用便捷;不支持定时获取锁功能;synchronized
在等待获取锁时不响应中断,不抛出中断异常,只记录中断状态
公平锁和非公平锁之间最大的区别在哪里(一句话版本)?
- 公平锁:先到临界区的线程一定会比后到的先获得锁
- 非公平锁:先到临界区的线程不一定比后到的先获得锁
synchronized加锁是公平锁还是非公平锁?
synchronized是非公平锁
如果使用
synchronized锁,在线程到达临界区时就直接CAS尝试获取锁,如果失败则升级为轻量级锁,再不断CAS请求锁。当CAS失败到达一定次数之后,升级为重量级锁,放入monitor对象的队列中阻塞等待。而且入队之前也会先尝试获取锁,获取不到才进入等待队列
因此,线程获取
synchronized锁都不会关心有没有其他线程之前获取过,所以
synchronized是非公平锁
为什么要设置前驱节点的状态为
SIGNAL?
为了表示前驱节点的后继节点对应的线程需要被唤醒,就这么简单
相关文章推荐
- JAVA多线程详解(三):ReentrantLock实现原理与源码分析
- ReentrantLock 源码分析 - AbstractQueuedSynchronizer 详解(二)
- ReentrantLock源码详解--公平锁、非公平锁
- 从ReentrantLock详解AQS原理源码解析
- ReentrantLock源码详解--条件锁
- ReentrantLock详解及源码分析
- 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
- ReentrantLock源码
- ReentrantLock源码解读,转载请注明出处!
- ReentrantLock使用详解(3)之测试锁与超时
- java ReentrantLock源码 分析 妥妥的
- Thread详解13:ReentrantLock的用法(一)
- java线程并发控制:ReentrantLock Condition使用详解
- Java并发系列[5]----ReentrantLock源码分析
- ReentrantLock源码分析
- ReentrantLock非公平锁加锁流程以及源码解读
- ReentrantLock源码解析(三):总结
- java锁的语义及ReentrantLock源码剖析
- java.util.concurrent.locks.ReentrantLock 源码剖析
- Java并发--互斥同步--Java两种锁机制synchronized和ReentrantLock详解