深入了解ReentrantLock中的公平锁和非公平锁的加锁机制
ReentrantLock和synchronized一样都是实现线程同步,但是像比synchronized它更加灵活、强大、增加了轮询、超时、中断等高级功能,可以更加精细化的控制线程同步,它是基于AQS实现的锁,他支持公平锁和非公平锁,同时他也是可重入锁和自旋锁。
本章将基于源码来探索一下ReentrantLock的加锁机制,文中如果存在理解不到位的地方,还请提出宝贵意见共同探讨,不吝赐教。
公平锁和非公平锁的加锁机制流程图:

一、ReentrantLock的公平锁
使用ReentrantLock的公平锁,调用lock进行加锁,lock方法的源码如下:
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
可以看到,FairLock首先调用了tryAcquire,tryAcquire源码如下:
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
从源码中可以看到,当state为0,即没有线程获取到锁时,FairLock首先会调用hasQueuedPredecessors()方法检查队列中是否有等待的线程或者自己是否在队列头部,如果队列中不存在等待的线程或者自己在队列头部则调用compareAndSetState()方法基于CAS操作进行加锁,如果CAS操作成功,则调用setExclusiveOwnerThread设置加锁线程为当前线程。
当state不为0,即有线程占用锁的时候会判断占有锁的线程是否是当前线程,如果是的话则可以直接获取到锁,这就是ReentrantLock是可重入锁的体现。
如果通过调用tryAcquire没有获取到锁,从源码中我们可以看到,FairLock会调用addWaiter()方法将当前线程加入CLH队列中,addWaiter方法源码如下:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
//基于CAS将当前线程节点加入队列尾部
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果CAS操作失败,则调用enq自旋加入队列
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在addWaiter方法中,会CAS操作将当前线程节点加入队列尾部,如果第一次CAS失败,则会调用enq方法通过自旋的方式,多次尝试进行CAS操作将当前线程加入队列。
将当前线程加入队列之后,会调用acquireQueued方法实现当前线程的自旋加锁,acquireQueued源码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在acquireQueued方法中每次自旋首先会调用predecessor()方法获取,当前线程节点的前节点,如果发现前节点是head节点,则说明当前线程节点处于对头(head是傀儡节点),那么则调用tryAcquire尽心加锁。
如果当前线程节点不在队列头部,那么则会调用shouldParkAfterFailedAcquire方法判断当前线程节点是否可以挂起知道前节点释放锁时唤醒自己,如果可以挂起,则调用parkAndCheckInterrupt实现挂起操作。
shouldParkAfterFailedAcquire源码如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
shouldParkAfterFailedAcquire源码中,如果当前线程节点的前节点的waitStatus状态为SIGNAL(-1)时,表明前节点已经设置了释放锁时唤醒(unpark)它的后节点,那么当前线程节点可以安心阻塞(park),等待它的前节点在unlock时唤醒自己继续尝试加锁。
如果前节点的waitStatus状态>0,即为CANCELLED (1),表明前节点已经放弃了获取锁,那么则会继续往前找,找到一个能够在unlock时唤醒自己的线程节点为止。如果前节点waitStatus状态是CONDITION (-2),即处于等待条件的状态,则会基于CAS尝试设置前节点状态为SIGNAL(主动干预前节点达到唤醒自己的目的)。
parkAndCheckInterrupt源码:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
二、ReentrantLock的非公平锁
和公平锁加锁机制不同的是,非公平锁一上来不管队列中是否还存在线程,就直接使用CAS操作进行尝试加锁(这就是它的非公平的体现),源码如下:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
如果CAS操作失败(一上来就吃了个闭门羹),则调用acquire方法进行后续的尝试和等待。从源码中可以看到,首先回调用tryAcquire方法进行再次尝试加锁或者锁重入,NoFairLockd的tryAcquire方法源码如下:
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;
}
可以看到NoFairLock的tryAcquire方法和FairLock的tryAcquire方法唯一不同之处是NoFairLock中尝试加锁前不需要调用hasQueuedPredecessors方法判断队列中是否存在其他线程,而是直接进行CAS操作加锁。
那么如果再次尝试加锁或者锁重入失败,则会进行后续的和公平锁完全一样的操作流程(不再赘述),即:加入队列(addWaiter)–>自旋加锁(acquireQueued)。另外,关注Java知音公众号,回复“后端面试”,送你一份面试题宝典!
三、unlock解锁
说完了公平锁和非公平锁的加锁机制,我们再顺带简单的看看解锁源码。unlock源码如下:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
Node h = head;
//锁释放成后唤醒后边阻塞的线程节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
总结 本文主要探索了公平锁和非公平锁的加锁流程,他们获取锁的不同点和相同点。整篇文章涉及到了以下几点:
- 公平锁、非公平锁加锁过程
- 自旋锁的实现以及自旋过程中的阻塞唤醒
- 可重入锁的实现
- CLH队列
转载:blog.csdn.net/qq_40400960/article/details/114242448
深入了解ReentrantLock中的公平锁和非公平锁的加锁机制的更多相关文章
- java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁
公平锁与非公平锁 ReentrantLock有一个很大的特点,就是可以指定锁是公平锁还是非公平锁,公平锁表示线程获取锁的顺序是按照线程排队的顺序来分配的,而非公平锁就是一种获取锁的抢占机制,是随机获得 ...
- ReentrantLock中的公平锁与非公平锁
简介 ReentrantLock是一种可重入锁,可以等同于synchronized的使用,但是比synchronized更加的强大.灵活. 一个可重入的排他锁,它具有与使用 synchronized ...
- Java中的公平锁和非公平锁实现详解
前言 Java语言中有许多原生线程安全的数据结构,比如ArrayBlockingQueue.CopyOnWriteArrayList.LinkedBlockingQueue,它们线程安全的实现方式并非 ...
- 深入分析ReentrantLock公平锁和非公平锁的区别
在ReentrantLock中包含了公平锁和非公平锁两种锁,通过查看源码可以看到这两种锁都是继承自Sync,而Sync又继承自AbstractQueuedSynchronizer,而AbstractQ ...
- Java之ReentrantLock公平锁和非公平锁
在Java的ReentrantLock构造函数中提供了两种锁:创建公平锁和非公平锁(默认).代码如下: public ReentrantLock() { sync = new NonfairSync( ...
- 第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()
最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...
- 深入分析ReentrantLock公平锁和非公平锁的区别 (转)
在ReentrantLock中包含了公平锁和非公平锁两种锁,通过查看源码可以看到这两种锁都是继承自Sync,而Sync又继承自AbstractQueuedSynchronizer,而AbstractQ ...
- 理解ReentrantLock的公平锁和非公平锁
学习AQS的时候,了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列,同时会阻塞当 ...
- 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...
随机推荐
- epoll反应堆模型实现
epoll反应堆模型demo实现 在高并发TCP请求中,为了实现资源的节省,效率的提升,Epoll逐渐替代了之前的select和poll,它在用户层上规避了忙轮询这种效率不高的监听方式,epoll的时 ...
- 32、python并发编程之背景知识
目录: 一 引子 二 为什么要有操作系统 三 什么是操作系统 四 操作系统与普通软件的区别 五 操作系统发展史 六 总结视频链接: 一 引子 顾名思义,进程即正在执行的一个过程.进程是对正在运行程序的 ...
- Solution -「多校联训」排水系统
\(\mathcal{Description}\) Link. 在 NOIP 2020 A 的基础上,每条边赋权值 \(a_i\),随机恰好一条边断掉,第 \(i\) 条段的概率正比于 \(a ...
- Solution -「CF 1023F」Mobile Phone Network
\(\mathcal{Description}\) Link. 有一个 \(n\) 个结点的图,并给定 \(m_1\) 条无向带权黑边,\(m_2\) 条无向无权白边.你需要为每条白边指定边权 ...
- 利用脚本快速执行Dockerfile以及docker镜像的启停与删除
`关于脚本:` 'sh setup.sh build' # 将Dockerfile创建成镜像 'sh setup.sh run' # 启动build创建好的镜像,放到后台运行 'sh setup.sh ...
- 常用环境变量配置(vim /etc/profile)
安装,参考:https://www.cnblogs.com/uncleyong/category/1457906.html # jdk export JAVA_HOME=/usr/local/jdk1 ...
- 100G/40G/25G/10G网络测试解决方案
一.100G概述 随着CDN等视频直播业务和P2P业务的快速发展,带宽的要求越来越高.当前5G业务势头正盛,其基于400G的主干网络通信业务也在积极部署之中.但当前在很多的业务场景中,100G系统的部 ...
- oracle 12c RAC 重启
转至:https://blog.csdn.net/weixin_40283570/article/details/81511072 关闭顺序 :关闭PDB----->关闭数据库------> ...
- Qt:Qt资源系统
学习自 Qt 资源系统(Qt Resource System) - 知乎 1.什么是Qt 资源系统 Qt资源系统是一种将图片.数据存储于二进制文件中的一套系统.这些图片.数据会被我们的程序使用,它们称 ...
- Java:IO流(二)——InputStream/OutputStream具体用法:FileXXXStream、ByteArrayXXXStream
1.说明 InputStream和OutputStream是Java标准库中最基本的IO流,它们都位于java.io包中,该包提供了所有同步IO的功能. 2.模块:java.io.InputStrea ...