聊聊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 ...
随机推荐
- windows下安装redis集群
前几天在自己在本机win10 电脑下部署了redis集群. 主要通过的是网上两个博客: 如何在windows下部署redis集群:https://blog.csdn.net/zsg88/article ...
- 编写shell脚本的规范
目录 编写shell脚本的一些规范 解释器 添加脚本版本和注释功能 添加脚本调试 变量命名 全局变量和局部变量 命名规范 函数命名 脚本命名 函数 引用模块或文件 脚本日志 配置文件 其他 编写she ...
- Jmeter CSV数据文件设置使用之一
第一步: 在Jmeter 里,新建CSV数据文件设置,选择对应的文件,变量名称根据需要自己取,如bug,test,如下图所示: 第二步: 配置Jmetet 数据源,参数对应的名称要与CSV数据文件设置 ...
- 汉诺塔问题实验--一个简洁的JAVA程序
思路: 这里使用递归法 n==1的时候,直接把它从x移到z位置即可. 如果是n层,我们首先把上面的n- 1层移到y位置,然后把最 下面的那个最大的盘子,移到z位置,然后把y上面放的上面n-1层移到z位 ...
- 自定义MFC对话窗口的类名
默认情况下,MFC对话框的窗口类名为"#32770",如果想自定义窗口类名呢,需要两步: 1.修改rc文件 这一步需要直接编辑rc文件,使用任意记事本工具即可,找到窗口的相关定义, ...
- 为什么大多数IOC容器使用ApplicationContext,而不用BeanFactory
1. 引言 Spring框架附带了两个IOC容器– BeanFactory 和 ApplicationContext. BeanFactory是IOC容器的最基本版本,ApplicationConte ...
- c# ToolStrip控件图片和文字显示--原创
如上图达到这样的效果 首先我们给属性Image和Text分别赋予需要显示的图片和文字 然后设置DisplyStyle属性为ImageAndText,意为同时显示图片和文字 各种设置ImageAlign ...
- Docker - 解决创建 tomcat 容器镜像却无法访问页面的问题
问题背景 查看 tomcat 镜像 docker images 运行并创建 tomcat 容器 docker -d -p 8888:8080 --name=tomcat2 tomcat:latest ...
- linux 协议栈 实现--编码小知识分析
unlikely 以及likely 作用: rcu_read_lock 以及rcu_read_unlock 作用: rcu_dereference .rcu_dereference_protecte ...
- TIP/Collision-Free Video Synopsis Incorporating Object Speed and Size Changes Code
代码地址 https://github.com/scutlzk/Collision-Free-Video-Synopsis-Incorporating-Object-Speed-and-Size-C ...