聊聊ReentrantLock实现原理
ReentrantLock 是常用的锁,相对于Synchronized ,lock锁更人性化,阅读性更强
从LOCK切入
考虑下面的场景如果有A,B线程,同时去执行lock.lock(Lock lock = new ReentrantLock 为全局属性),当A线程抢到锁以后,此时B线程做了哪些事情
要知道B线程做了那些事情,就要知道一个类AbstractQueueSynchronizer(简称AQS),它负责将锁竞争失败线程存储起来
Lock lock = new ReentrantLock();
try{
lock.lock();
System.out.println("测试");
}finally {
lock.unlock();
}
AQS 分析
要理解锁的运行原理,至少要了解下面的几个问题
①,如何锁定(或者说锁定的标准是什么)
②,竞争锁失败,怎么处理
③,如何释放锁
④,如何唤醒其他线程重新竞争
针对上面的4个问题,分析一下ReenTrantLock
如何锁定
//当执行lock.lock()竞争锁
final void lock() {
//1,A,B线程竞争ReenTrantLock->AQS属性->state属性
//2,乐观锁的形式更新state,如A线程先将state更新为1,就代表A线程竞争锁成功
if (compareAndSetState(0, 1))
//设置锁持有线程
setExclusiveOwnerThread(Thread.currentThread());
else
//B线程竞争锁失败,执行下面方法
acquire(1);
}
要理解AQS的原理中,理解state的状态值变化很重要
state=0 ,node节点创建时state=0
state=-1,后继线程可以考虑阻塞啦,因为后继线程不是下一个需要执行竞争锁的(在添加一个新的节点后,那么的它的pre节点的state会变成-1)
state = 1,节点失效,如:当tryLock(1000,TimeUnit.MILLISECOND) 超时时,节点状态值
B线程竞争锁失败,处理方式
1,尝试重新获取锁(目的)
1.1,看看前面锁是否释放(先下手为强,假如成功尼,哈哈,满满的求生欲)
1.2,查看是否同一个线程竞争锁,考虑重入锁
2,仍然失败,将B线程封装一个新的node节点,插入一个双向队列的尾部
3,开始自旋(死循环),直到获取锁为止
3.1,判断当前线程是否有资格竞争锁
3.2,无资格或者竞争失败的情况下,考虑是否暂停B线程线程(A线程释放锁的时候,会唤醒B线程)
从源码角度,来解析一下上面的内容
public final void acquire(int arg) {
//尝试获取锁,失败,考虑可重入锁
if (!tryAcquire(arg) &&
//addWaiter 添加到双向队列的尾结点
//acquireQueued,开始自旋(死循环)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果当前线程在竞争过程中存在中断情况,设置当前线程中断
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//判断当前这个节点前节点是否是head节点,是的话,重新进行竞争操作(此时A线程可能还木有释放锁)
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果失败或者前节点不是head节点,根据实际情况,考虑一下暂停当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
背景:A线程竞争锁成功,B线程竞争锁失败,AQS队列为空,此时B线程准备加入队列
1,首先添加一个空节点,head,tail 链接到此节点

2,B线程节点,加入AQS队列,A线程释放锁的时候,直接唤醒B线程竞争锁,竞争成功,队列前进一位即可

A线程释放锁
1,做减法--->AQS属性state一直被减到0(考虑可重入锁),代表这个A线程彻底释放锁
2,在双向队列中,查找下一个节点,唤醒这个节点的线程
如果A线程直接抢到锁啦说明A线程所在的节点并木有添加到双向队列中,那么下一个节点就是双向队列的头节点
如果A线程所在的节点加入了双向队列,那么head节点为A线程所在的节点,下一个节点为head节点的下一个节点
public void unlock() {
sync.release(1);
}
//做减法--->AQS属性state一直被减到0(考虑可重入锁),代表这个A线程彻底释放锁
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;
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//在双向队列中,查找下一个节点,唤醒这个节点的线程
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
聊聊ReentrantLock实现原理的更多相关文章
- ReentrantLock实现原理深入探究
前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写ReentrantLock的使用.ReentrantLock和synchronized的区别的文章很多,研究ReentrantLoc ...
- (转)ReentrantLock实现原理及源码分析
背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...
- 【Java并发编程】15、ReentrantLock实现原理深入探究
原文已经写得非常详细了,直接把大神的文章转发过来了 https://www.cnblogs.com/xrq730/p/4979021.html 前言 这篇文章被归到Java基础分类中,其实真的一点都 ...
- ReentrantLock实现原理
以下是本篇文章的大纲 1 synchronized和lock 1.1 synchronized的局限性 1.2 Lock简介 2 AQS 3 lock()与unlock()实现原理 3.1 基础知识 ...
- ReentrantLock实现原理及源码分析
ReentrantLock是Java并发包中提供的一个可重入的互斥锁.ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性.只不过相比原生的Sync ...
- ReentrantLock 实现原理
使用 synchronize 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现. 而 ReentrantLock 就是一个普通的类,它是基于 AQS(Abstr ...
- 解析ReentrantLock实现原理
在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock(Lock的实现主要有ReentrantLock.ReadLock和WriteLock).synchronize ...
- 聊聊ReentrantLock的内部实现
大家都用过ReentrantLock,但是大家对内部实现是否足够了解呢,下面我就简单说一下其中的实现原理. ReentrantLock是可重入锁,也就是同一个线程可以多次获取锁,每获取一次就会进行一次 ...
- ReentrantLock的原理解析
重入锁(ReentrantLock)是一种可重入无阻塞的同步机制.性能同synchronized接近(老版本jdk中性能很差). 下面重点看下常用的lock()和unlock()方法的实现原理. lo ...
随机推荐
- 一个Task.Factory.StartNew的错误用法
同事写了这样一段代码: FactoryStartNew类: using System; using System.Collections.Generic; using System.Linq; usi ...
- JAVA类库之——Math类(持续更新)
Math类 目录 Math类 1.Math 类中的常量方法 返回π(圆周率)值的方法:PI 返回E(自然对数低)值的方法:E 2.Math 类中的三角函数方法 计算正弦函数值的方法:Sin(radia ...
- DataStructure-enum枚举
Enum:枚举类型 enum模块定义了一个提供迭代和比较功能的枚举类型.可以用这个模块为值创建明确定义的符号,而不是使用字面量整数或字符串. 创建枚举 可以使用class派生Enum,然后增加描述值得 ...
- Miniconda 安装 & Pip module 安装 & Shell 脚本调用 Miniconda 虚拟环境手册(实战项目应用)
(实战项目应用) 1. 下载Miniconda 两个安装方式: 方式1:wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Min ...
- insert into select 和select into from 备份表
一 insert into select要求表必须存在 INSERTINTO order_record SELECT * FROM order_today FORCEINDEX (idx_pay_su ...
- Java-GUI基础(三)java.swing
1. 简介 swing与awt:可以认为awt是swing的前身,awt即Abstrace Window Toolkit抽象窗口工具包,swing是为了解决awt在开发中的问题而开发的,是awt的改良 ...
- Spark: JAVA_HOME is not set
在Spark项目,sbin目录下的spark-config.sh文件下,最后添加JAVA_HOME的索引. 即可.
- 【Luogu】 P6274 [eJOI2017]六 题解
首先,题目说了最多\(6\)个质因数. 如此小的数据范围,不是状压还是啥? 然后,我们可以发现一个性质:只要两个因数有相同的质因数(不管次数是多少),两者就不互质. 这启示我们用一个二进制数来表示一类 ...
- 左值 lvalue,右值 rvalue 和 移动语义 std::move
参考文章: [1] 基础篇:lvalue,rvalue和move [2] 深入浅出 C++ 右值引用 [3] Modern CPP Tutorial [4] 右值引用与转移语义 刷 Leetcode ...
- Java读取Excel报错Unable to recognize OLE stream
Unable to recognize OLE stream 的解决方法 将xlsx用excel打开并另存为2003的xls,然后再运行即可解决问题 File file = new File(&quo ...