并发编程(学习笔记-共享模型之JUC-ReentrantLock原理)-part6
Posted LL.LEBRON
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程(学习笔记-共享模型之JUC-ReentrantLock原理)-part6相关的知识,希望对你有一定的参考价值。
文章目录
本文章视频指路👉 黑马程序员-并发编程
ReentrantLock原理
1.非公平锁实现原理
先从构造器开始看,默认为非公平锁实现:
//默认非公平锁
public ReentrantLock()
sync = new NonfairSync();
NonfairSync
继承自 AQS
。
没有竞争时:
第一个竞争出现时:
Thread-1 执行了:
CAS
尝试将 state 由 0 改为 1,结果失败- 进入
tryAcquire
逻辑,这时 state 已经是1,结果仍然失败 - 接下来进入
addWaiter
逻辑,构造 Node 队列- 图中黄色三角表示该 Node 的 waitStatus 状态,其中
0
为默认正常状态 - Node 的创建是懒惰的
- 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程
- 图中黄色三角表示该 Node 的 waitStatus 状态,其中
当前线程进入 acquireQueued
逻辑:
-
acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入
park
阻塞 -
如果自己是紧邻着 head(排第二位),那么再次
tryAcquire
尝试获取锁,当然这时 state 仍为 1,失败 -
进入
shouldParkAfterFailedAcquire
逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false -
shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
-
当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是
-1
,这次返回 true -
进入
parkAndCheckInterrupt
, Thread-1 park(灰色表示)
再次有多个线程经历上述过程竞争失败,变成这个样子:
Thread-0 释放锁,进入 tryRelease
流程,如果成功:
- 设置
exclusiveOwnerThread
为 null - state = 0
当前队列不为 null,并且 head 的 waitStatus = -1
,进入 unparkSuccessor
流程。
找到队列中离 head 最近的一个 Node(没取消的),unpark
恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued
流程
如果加锁成功(没有竞争),会设置:
- exclusiveOwnerThread 为 Thread-1,state = 1
- head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
- 原本的 head 因为从链表断开,而可被垃圾回收
如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了
如果不巧又被 Thread-4 占了先
- Thread-4 被设置为 exclusiveOwnerThread,state = 1
- Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞
2.可重入原理
这里以非公平锁为例:
获取锁:
// ReentrantLock.Sync.nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires)
//获取当前线程
final Thread current = Thread.currentThread();
//获取状态值
int c = getState();
if (c == 0)
//如果状态变量为0,再次尝试CAS更新状态变量的值
//相对于公平锁模式少了!hasQueuedPredecessors()条件
if (compareAndSetState(0, acquires))
setExclusiveOwnerThread(current);
return true;
// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
else if (current == getExclusiveOwnerThread())
//state++
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
return false;
释放锁:
//ReentrantLock.Sync.tryRelease
protected final boolean tryRelease(int releases)
//state--
int c = getState() - releases;
// 如果当前线程不是占有着锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果状态变量的值为0了,说明完全释放了锁
//这也就是为什么重入锁调用了多少次lock()就要调用多少次unlock()的原因
//如果不这样做,会导致锁不会完全释放,别的线程永远无法获取到锁
if (c == 0)
free = true;
// 清空占有线程
setExclusiveOwnerThread(null);
//设置状态变量的值
setState(c);
return free;
可以看出源码中通过state
变量的计数来实现可重入,只有当state==0
时才说明已经不持有锁。
3.可打断原理
3.1 不可打断模式
在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了。
//AbstractQueuedSynchronizer.acquireQueued()
final boolean acquireQueued(final Node node, int arg)
//失败标记
boolean failed = true;
try
//中断标记
boolean interrupted = false;
//自旋
for (;;)
//当前节点的前一个节点
final Node p = node.predecessor();
//如果当前节点的前一个节点为head节点,则说明轮到自己获取锁了
//调用ReentrantLock.FairSync.tryAcquire()方法再次尝试获取锁
if (p == head && tryAcquire(arg))
//尝试获取锁成功
//这里同时只会有一个线程在执行,所以不需要用CAS更新
//把当前节点设置为新的头节点
setHead(node);
//并把上一个节点从链表中删除
p.next = null; // help GC
//标记为未失败
failed = false;
//还是需要获得锁后, 才能返回打断状态
return interrupted;
//是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
//真正阻塞的方法
parkAndCheckInterrupt())
// 如果是因为 interrupt 被唤醒, 返回打断状态为 true
interrupted = true;
finally
//如果失败了
if (failed)
cancelAcquire(node);
//AbstractQueuedSynchronizer.parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt()
//如果打断标记已经是 true, 则 park 会失效
LockSupport.park(this);
//interrupted 会清除打断标记
//清除打断标记,下次park就不会受到影响
return Thread.interrupted();
public final void acquire(int arg)
if (
!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
)
// 如果打断状态为 true,acquireQueued方法就会返回成功。就会执行这个方法
selfInterrupt();
static void selfInterrupt()
// 重新产生一次中断
Thread.currentThread().interrupt();
3.2 可打断模式
与不可打断模式不同的是,当被interrupt
时是会抛出异常的,不会继续执行for循环。此时等待的线程可以停止等待。
//ReentrantLock.lockInterruptibly()
public void lockInterruptibly() throws InterruptedException
sync.acquireInterruptibly(1);
//AbstractQueuedSynchronizer.acquireInterruptibly(int arg)
public final void acquireInterruptibly(int arg)
throws InterruptedException
if (Thread.interrupted())
throw new InterruptedException();
//如果没有获得到锁,进入doAcquireInterruptibly方法
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
private void doAcquireInterruptibly(int arg)
throws InterruptedException
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try
for (; ; )
final Node p = node.predecessor();
if (p == head && tryAcquire(arg))
setHead(node);
p.next = null; // help GC
failed = false;
return;
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//在park过程中如果被interrupt会进入这里
//这时候抛出一次,而不会再次进入for循环
throw new InterruptedException();
finally
if (failed)
cancelAcquire(node);
4.公平锁实现原理
//ReentrantLock.FairSync.lock()
final void lock()
//调用AQS的acquire()方法获取锁
//注意,这里传的值为1
acquire(1);
//AbstractQueuedSynchronizer.acquire()
public final void acquire(int arg)
//先尝试加锁
//如果失败了,就排队
if (!tryAcquire(arg) &&
//注意addWaiter()这里传入的节点模式为“独占模式”
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
//ReentrantLock.FairSync.tryAcquire()
//与非公平锁主要区别在于 tryAcquire 方法的实现
protected final boolean tryAcquire(int acquires)
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前状态变量的值
int c = getState();
//如果为0,说明现在还没有人占有锁
if (c == 0)
//先检查 AQS 队列中是否有前驱节点, 没有才去竞争
//如果没有其他线程在排队,那么当前线程尝试更新state的值为1
//如果成功了,说明当前线程获取了锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires))
//当前线程获取了锁,把自己设置到exclusiveOwnerThread中
//exclusiveOwnerThread是AQS的父类AbstractOwnableSynchronizer中提供的变量
setExclusiveOwnerThread(current);
//返回true,说明成功获得了锁
return true;
//如果当前线程本身就占有锁,现在又尝试获取锁
//那么,直接让他获取锁,并返回true
else if (current == getExclusiveOwnerThread())
//状态变量state的值加一
int nextc = c + acquires;
//如果发送一出,则报错
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//这里为什么不需要CAS更新state?
//因为当前线程占有锁,其他线程只会CAS把state从0更新到1,是不会成功的
//所以不存在竞争,自然不需要使用CAS来更新
setState(nextc);
//当线程获取锁成功
return true;
//当线程获取锁失败
return false;
public final boolean hasQueuedPredecessors()
//尾部
Node t = tail;
//头部
Node h = head;
Node s;
// h != t 时表示队列中有 Node
return h != t &&
// (s = h.next) == null 表示队列中还有没有老二
((s = h.ext) == null ||
// 或者队列中老二线程不是此线程
s.thread != Thread.currentThread());
5.条件变量实现原理
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
5.1 await 流程
开始 Thread-0 持有锁,调用 await
,进入 ConditionObject 的 addConditionWaiter
流程
创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
接下来进入 AQS 的 fullyRelease
流程,释放同步器上的锁
unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功
park 阻塞 Thread-0
5.2 signal 流程
假设 Thread-1 调用signal
要来唤醒 Thread-0
进入 ConditionObject 的 doSignal
流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node
执行 transferForSignal
流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的 waitStatus 改为 -1
(waitStatus == 0 默认状态,waitStatus == -1 表示当前node如果是head节点时,释放锁之后需要唤醒它的后继节点)
Thread-1 释放锁,进入 unlock 流程,略
以上是关于并发编程(学习笔记-共享模型之JUC-ReentrantLock原理)-part6的主要内容,如果未能解决你的问题,请参考以下文章
并发编程(学习笔记-共享模型之JUC-ReentrantLock原理)-part6