Java多线程(二)——ReentrantLock源码解析(补充3——tryLock(long,TimeUnit) 锁超时)
admin
2024-05-16 06:29:54
0

ReentrantLock源码解析(补充3——tryLock(long,TimeUnit)锁超时)

上一章仅介绍了 ReentrantLock 的常用方法以及公平锁、非公平锁的实现。这里对上一章做一些补充。主要是:

  • AQS 中阻塞的线程被唤醒后的执行流程
  • 可打断的锁 lock.lockInterruptibly()
  • 锁超时 lock.tryLock(long,TimeUnit) (本篇讲述)
  • 条件变量 Condition

1. 锁超时的应用

lock.tryLock(long,TimeUnit) 和 lock.tryLock() 是不同的。

  • lock.tryLock() 是非公平锁实现,仅尝试获取锁一次
  • lock.tryLock(long, TimeUnit) 调用链分为公平与非公平锁,
    • 在限定时间内尝试获取锁,如果时间结束仍然获取不到,将返回false
    • 限定时间内可以被中断等待。

使用方法:

public class ReentrantLockDemo1 {ReentrantLock lock = new ReentrantLock();Runnable r = new Runnable() {@Overridepublic void run() {try {//尝试在2s内获取到锁if(!lock.tryLock(2,TimeUnit.SECONDS)){System.out.println(Thread.currentThread().getName()+" 2s内获取不到锁");return;}else{System.out.println(Thread.currentThread().getName()+" 2s内获取到了锁");}} catch (InterruptedException e) {//如果在限定时间内被中断,则响应中断,并放弃等待锁资源e.printStackTrace();System.out.println(Thread.currentThread().getName()+" 2s内还未获取到锁,且被中断退出等待");return;}try{//临界区System.out.println(Thread.currentThread().getName()+" 获取到了锁,进入临界区");}finally {//释放锁lock.unlock();}}};public static void main(String[] args)  {ReentrantLockDemo1 demo = new ReentrantLockDemo1();Thread t1= new Thread(demo.r,"t1");//主线程先获取到锁demo.lock.lock();System.out.println(Thread.currentThread().getName()+" 获取到锁");t1.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}//主线程等待一段时间后才释放锁System.out.println(Thread.currentThread().getName()+" 释放了锁");demo.lock.unlock();}}

上述代码运行结果:

main 获取到锁
t1 2s内获取不到锁
main 释放了锁Process finished with exit code 0

由于主线程持有锁 5 秒,而线程 t1 获取锁的限定时间为 2 秒,限定时间后没有获取到锁,放弃锁资源的等待。

如果让主线程中等待时间缩减为 Thread.sleep(50),运行结果如下:

main 获取到锁
main 释放了锁
t1 2s内获取到了锁
t1 获取到了锁,进入临界区Process finished with exit code 0

2. tryLock(long, TimeUnit) 的实现原理

通过调用 lock.tryLock( long , TimeUnit ) 进入到了 AQS 的 tryAcquireNanos(int arg, long) 方法:

//ReentrantLock
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
//AbstractQueuedSynchronizer
public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);
}

tryAcquireNanos(int , long) 是一个模板方法,且由于 tryAcquire() 方法需要子类实现,所以分为了公平与非公平锁两种情况。

  • 公平锁:tryAcquire() 如果锁空闲,将先查看 AQS 等待队列中是否等待节点,如果没有才会CAS一次来尝试获取锁资源。如果锁非空闲,可重入。
  • 非公平锁:tryAcquire() 如果锁空闲,直接CAS一次来尝试获取锁资源。如果锁非空闲,可重入。

在 doAcquireNanos() 方法中核心通过 LockSupport.parkNanos() 来定时唤醒并查看是否限时结束。

private boolean doAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {//如果等待时间不合法,直接退出if (nanosTimeout <= 0L)return false;//计算截止时间final long deadline = System.nanoTime() + nanosTimeout;//封装到node结构中,并加到AQS的等待队列队尾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 GCfailed = false;return true;}//如果获取不到锁资源,计算剩余时间nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L)//剩余时间没了,则退出等待return false;//如果还有剩余时间if (shouldParkAfterFailedAcquire(p, node) &&//如果剩余时间 > 自旋时间阈值nanosTimeout > spinForTimeoutThreshold)//将线程阻塞挂起直到剩余时间结束//1. 中途可以被interrupt()中断,并进入下方的抛出异常响应中断//2. 唤醒后,再次进入 for(;;) 循环,最后进行一次锁资源的获取LockSupport.parkNanos(this, nanosTimeout);if (Thread.interrupted())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}

一般情况下,进入 doAcquireNanos() 将会先时获取一次锁,如果没获取到,则:

  • 如果剩余时间 < 自旋尝试获取锁时间阈值:不进行阻塞,而是再次进入 for( ; ; )循环,形成自旋尝试获取锁的动作。
  • 如果剩余时间 > 自旋尝试获取锁时间阈值:剩余多少时间就阻塞多少时间,唤醒后,再次进入 for( ; ; ) 循环,最后一次尝试锁的获取。

不论是自旋获取锁,还是阻塞等待,都可以检测到线程 interrupt() 的情况,如果中断标记为 true, 可以响应中断,即抛出 InterruptedException 异常。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...