多线程系列(九) -ReentrantLock常用方法详解
一、简介
在上一篇文章中,我们介绍了ReentrantLock
类的一些基本用法,今天我们重点来介绍一下ReentrantLock
其它的常用方法,以便对ReentrantLock
类的使用有更深入的理解。
二、常用方法介绍
2.1、构造方法
ReentrantLock
类有两个构造方法,核心源码内容如下:
/**
* 默认创建非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* fair为true表示是公平锁,fair为false表示是非公平锁
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
相比于synchronized
同步锁,ReentrantLock
有一个很大的特点,就是开发人员可以手动指定采用公平锁机制还是非公平锁机制。
公平锁:顾名思义,就是每个线程获取锁的顺序是按照线程排队的顺序来分配的,最前面的线程总是最先获取到锁。
- 优点:所有的线程都有机会得到资源
- 缺点:公平锁机制实现比较复杂,程序流程比较多,执行速度比较慢
非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,任何线程在某时刻都有可能直接获取并拥有锁,之前介绍的synchronized
其实就是一种非公平锁
- 优点:公平锁机制实现相对比较简单,程序流程比较少,执行速度比较快
- 缺点:有可能某些线程一直拿不到锁,导致饿死
ReentrantLock
默认的构造方法是非公平锁,如果想要构造公平锁机制,只需要传入true
就可以了。
示例代码如下:
public static void main(String[] args) {
// 创建公平锁实现机制
Lock lock = new ReentrantLock(true);
// 创建5个线程
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 启动了!");
// 尝试获取锁
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 获得锁!");
} finally {
lock.unlock();
}
}
}).start();
}
}
运行一下程序,结果如下:
ThreadName:Thread-0, 启动了!
ThreadName:Thread-1, 启动了!
ThreadName:Thread-0, 获得锁!
ThreadName:Thread-1, 获得锁!
ThreadName:Thread-2, 启动了!
ThreadName:Thread-2, 获得锁!
ThreadName:Thread-3, 启动了!
ThreadName:Thread-3, 获得锁!
ThreadName:Thread-4, 启动了!
ThreadName:Thread-4, 获得锁!
从日志上可以看到,启动顺序为0,1,2,3,4
,获取锁的顺序为0,1,2,3,4
,启动与获取锁的排队机制一致。
假如我们构造方法里面的把true
改成false
,也就是非公平锁机制,在看看运行效果,结果如下:
ThreadName:Thread-1, 启动了!
ThreadName:Thread-2, 启动了!
ThreadName:Thread-1, 获得锁!
ThreadName:Thread-0, 启动了!
ThreadName:Thread-2, 获得锁!
ThreadName:Thread-3, 启动了!
ThreadName:Thread-3, 获得锁!
ThreadName:Thread-0, 获得锁!
ThreadName:Thread-4, 启动了!
ThreadName:Thread-4, 获得锁!
从日志上可以看到,启动顺序为1,2,0,3,4
,获取锁的顺序为1,2,3,0,4
,线程启用与获取锁的顺序不一致。
从实际的运行结果看,非公平锁要比公平锁执行速度要快一些,当线程数越多的时候,效果越明显。
2.2、核心方法
ReentrantLock
类的核心方法就比较多了,如下表!
方法 | 描述 |
---|---|
public void lock() | 阻塞等待获取锁;不允许Thread.interrupt 中断,即使检测到Thread.isInterrupted 一样会继续尝试 |
public void lockInterruptibly() | 当前线程未被中断,则获取锁;允许在等待时由其它线程调用等待线程的Thread.interrupt 方法来中断等待线程的等待而直接返回 |
public boolean tryLock() | 尝试申请一个锁,在成功获得锁后返回true ,否则,立即返回false |
public boolean tryLock(long timeout, TimeUnit unit) | 在一段时间内尝试申请一个锁,在成功获得锁后返回true ,否则,立即返回false |
public void unlock() | 释放锁 |
public Condition newCondition() | 条件实例,用于线程等待/通知模式 |
public int getHoldCount() | 获取当前线程持有此锁的次数 |
public boolean isHeldByCurrentThread() | 检测是否被当前线程持有 |
public boolean isLocked() | 查询此锁是否由任意线程持有 |
public final boolean isFair() | 如果是公平锁返回true ,否则返回false |
public final boolean hasQueuedThreads() | 查询是否有线程正在等待 |
public final boolean hasQueuedThread(Thread thread) | 查询给定线程是否正在等待获取此锁 |
public final int getQueueLength() | 获取正等待获取此锁的线程数 |
public boolean hasWaiters(Condition condition) | 是否存在正在等待并符合相关给定条件的线程 |
public int getWaitQueueLength(Condition condition) | 正在等待并符合相关给定条件的线程数量 |
虽然方法很多,但是实际上常用方法就那么几个,下面我们主要抽一些常用的方法进行介绍。
2.2.1、tryLock 方法
lock()
、lockInterruptibly()
、tryLock()
和tryLock(long timeout, TimeUnit unit)
这几个方法,目的其实是一样的,都是为了获取锁,只是针对不同的场景做了单独的处理。
lock()
:阻塞等待获取锁,如果没有获取到会一直阻塞,即使检测到Thread.isInterrupted
一样会继续尝试;
lockInterruptibly()
:同样也是阻塞等待获取锁,稍有不同的是,允许在等待时由其它线程调用等待线程的Thread.interrupt
方法来中断等待线程的等待而直接返回tryLock()
:表示尝试申请一个锁,在成功获得锁后返回true
,否则,立即返回false
,不会阻塞等待获取锁tryLock(long timeout, TimeUnit unit)
:表示在一段时间内尝试申请一个锁,在成功获得锁后返回true
,否则,立即返回false
其中tryLock(long timeout, TimeUnit unit)
方法的应用最广泛,因为它能防止程序发生死锁,即使在一段时间内没有获取锁,也会自动退出,不会一直阻塞。
我们可以看一个简单的例子,如下!
public static void main(String[] args) {
// 创建公平锁实现机制
Lock lock = new ReentrantLock();
// 创建5个线程
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
boolean flag = false;
try {
// 尝试3秒内获取锁
flag = lock.tryLock(3, TimeUnit.SECONDS);
if(flag){
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 获取到锁");
// 模拟进行5秒的业务操作
Thread.sleep(5000);
} else {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 经过3秒钟的尝试未获取到锁,放弃尝试");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (flag){
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 释放对象");
lock.unlock();
}
}
}
}).start();
}
}
运行一下程序,结果如下:
ThreadName:Thread-0, 获取到锁
ThreadName:Thread-3, 经过3秒钟的尝试未获取到锁,放弃尝试
ThreadName:Thread-1, 经过3秒钟的尝试未获取到锁,放弃尝试
ThreadName:Thread-2, 经过3秒钟的尝试未获取到锁,放弃尝试
ThreadName:Thread-4, 经过3秒钟的尝试未获取到锁,放弃尝试
ThreadName:Thread-0, 释放对象
可以很清晰的看到,非Thread-0
线程尝试了 3 秒没有获取到锁,自动放弃;如果换成lock()
方法进行获取锁,线程Thread-0
如果不释放锁,其它线程会一直阻塞。
2.2.2、unlock 方法
unlock()
方法也是常用方法,表示释放锁。当获取到锁之后,一定要手动释放锁,否则可能会造成其它程序执行出现问题,通常用在finally
方法块里面。
// 阻塞等待获取锁
lock.lock();
try {
// 业务操作...
} finally {
// 一定要释放锁
lock.unlock();
}
2.2.3、newCondition 方法
newCondition()
方法,在上文中介绍过,ReentrantLock
和Condition
结合,可以实现线程之间的等待/通知模型。
简单的示例,如下!
public class Counter {
private final Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int count;
public void await(){
// 加锁
lock.lock();
try {
// 让当前线程进入等待状态,并释放锁
condition.await();
System.out.println("await等待结束,count:" + getCount());
} catch (Exception e){
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public void signal(){
// 加锁
lock.lock();
try {
count++;
// 唤醒某个等待线程
condition.signal();
System.out.println("signal 唤醒通知完毕");
} catch (Exception e){
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class MyThreadTest {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 先启动执行等待的线程
new Thread(new Runnable() {
@Override
public void run() {
counter.await();
}
}).start();
Thread.sleep(3000);
// 过3秒,再启动执行通知的线程
new Thread(new Runnable() {
@Override
public void run() {
counter.signal();
}
}).start();
}
}
运行一下程序,结果如下:
signal 唤醒通知完毕
await等待结束,count:1
2.2.4、getHoldCount 方法
getHoldCount()
方法的作用是返回的是当前线程调用lock()
的次数。
示例代码如下:
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
// 第一次获取锁
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" + lock.getHoldCount());
// 第二次获取锁
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" + lock.getHoldCount());
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
}).start();
}
运行一下程序,结果如下:
ThreadName:Thread-0, getHoldCount:1
ThreadName:Thread-0, getHoldCount:2
侧面也证明了一点,ReentrantLock
和synchronized
一样,锁都具有可重入特性,也就是说同一个线程多次调用同一个ReentrantLock
的lock()
方法,可以再次进入方法体,无需阻塞等待。
2.2.5、isLocked 方法
isHeldByCurrentThread()
和isLocked()
方法都是用于检测锁是否被持有。
其中isHeldByCurrentThread()
方法表示此锁是否由当前线程持有;isLocked()
方法表示此锁是否由任意线程持有。
我们看一个简单的示例,如下:
public class Counter {
private ReentrantLock lock = new ReentrantLock();
public void methodA(){
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 当前线程是否持有锁:" + lock.isHeldByCurrentThread());
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意线程是否持有锁:" + lock.isLocked());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void methodB(){
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 当前线程是否持有锁:" + lock.isHeldByCurrentThread());
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意线程是否持有锁:" + lock.isLocked());
}
}
public class MyThreadTest {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
new Thread(new Runnable() {
@Override
public void run() {
counter.methodA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
counter.methodB();
}
}).start();
}
}
运行一下程序,结果如下:
ThreadName:Thread-0, 当前线程是否持有锁:true
ThreadName:Thread-0, 任意线程是否持有锁:true
ThreadName:Thread-1, 当前线程是否持有锁:false
ThreadName:Thread-1, 任意线程是否持有锁:true
从日志结果很容易理解,Thread-0
线程持有锁,因此调用isHeldByCurrentThread()
和isLocked()
方法,返回结果都是true
;Thread-1
线程没有持有锁,因此isHeldByCurrentThread()
方法返回false
,isLocked()
方法返回true
2.2.6、isFair 方法
isFair()
方法用来获取此锁是否公平锁。
简单的示例,如下:
ReentrantLock lock = new ReentrantLock(true);
System.out.println("是否公平锁:" + lock.isFair());
输出结果如下:
是否公平锁:true
ReentrantLock
默认的是非公平锁,当通过构造方法显式传入true
时,采用的是公平锁机制
2.2.5、hasQueuedThreads 方法
hasQueuedThreads()
和hasQueuedThread()
方法都用于查询是否有线程等待获取锁,稍有不同的是:hasQueuedThreads()
方法表示查询是否有线程正在等待获取锁;hasQueuedThread()
方法表示查询给定线程是否正在等待获取此锁。
另外还有一个getQueueLength()
方法,表示获取正等待获取此锁的线程数。
我们看一个简单的示例,如下:
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadA.start();
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadB.start();
// 等待线程都启动完毕
Thread.sleep(1000);
System.out.println("查询是否有线程正在等待:" + lock.hasQueuedThreads());
System.out.println("查询处于等待的线程数:" + lock.getQueueLength());
System.out.println("threadA 是否处于等待状态:" + lock.hasQueuedThread(threadA));
System.out.println("threadB 是否处于等待状态:" + lock.hasQueuedThread(threadB));
}
输出结果如下:
查询是否有线程正在等待:true
查询处于等待的线程数:1
threadA 是否处于等待状态:false
threadB 是否处于等待状态:true
从日志上可以清晰的看到,线程threadA
先获取了锁,线程threadB
处于等待获取锁的状态,处于等待的线程数为1
。
2.2.7、hasWaiters 方法
hasWaiters()
和getWaitQueueLength()
方法,支持传入condition
条件对象进行查询。
其中hasWaiters()
方法表示查询是否存在正在等待并符合相关给定条件的线程;getWaitQueueLength()
方法表示查询正在等待并符合相关给定条件的线程数量。
我们看一个简单的示例,如下:
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
condition.await();
System.out.println("await等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadA.start();
// 睡眠1秒
Thread.sleep(1000);
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("是否存在正在等待并符合相关给定条件的线程:" + lock.hasWaiters(condition));
System.out.println("正在等待并符合相关给定条件的线程数量:" + lock.getWaitQueueLength(condition));
Thread.sleep(5000);
condition.signal();
System.out.println("signal 唤醒通知完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadB.start();
}
输出结果如下:
是否存在正在等待并符合相关给定条件的线程:true
正在等待并符合相关给定条件的线程数量:1
signal 唤醒通知完毕
await等待结束
需要注意的是,调用condition
对象的方法,必须要在获取锁的方法体内执行。
三、小结
本文主要围绕ReentrantLock
类的核心方法进行了一些知识总结,其中最常用方法的主要就两个,tryLock(long timeout, TimeUnit unit)
和unlock()
,通过它可以实现线程同步安全的效果。
本文内容比较多,如果有不正之处,请多多谅解,并欢迎批评指出。
四、参考
1、https://www.cnblogs.com/xrq730/p/4855538.html
多线程系列(九) -ReentrantLock常用方法详解的更多相关文章
- ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借
ASP.NET MVC深入浅出系列(持续更新) 一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...
- java多线程系列(九)---ArrayBlockingQueue源码分析
java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...
- $.ajax()常用方法详解(推荐)
AJAX 是一种与服务器交换数据的技术,可以在补充在整个页面的情况下更新网页的一部分.接下来通过本文给大家介绍ajax一些常用方法,大家有需要可以一起学习. 1.url: 要求为String类型的参数 ...
- 【Java】HashMap源码分析——常用方法详解
上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...
- phpExcel常用方法详解【附有php导出excel加超级链接】
phpExcel常用方法详解[附有php导出excel加超级链接] 发表于4年前(-- :) 阅读() | 评论() 0人收藏此文章, 我要收藏 赞0 http://www.codeplex.com/ ...
- Hexo系列(三) 常用命令详解
Hexo 框架可以帮助我们快速创建一个属于自己的博客网站,熟悉 Hexo 框架提供的命令有利于我们管理博客 1.hexo init hexo init 命令用于初始化本地文件夹为网站的根目录 $ he ...
- phpExcel常用方法详解
phpExcel常用方法详解[附有php导出excel加超级链接] 发表于4年前(2012-07-20 12:57) 阅读(510) | 评论(0) 0人收藏此文章, 我要收藏 赞0 http://w ...
- Signalr系列之虚拟目录详解与应用中的CDN加速实战
目录 对SignalR不了解的人可以直接移步下面的目录 SignalR系列目录 前言 前段时间一直有人问我 在用SignalR 2.0开发客服系统[系列1:实现群发通讯]这篇文章中的"/Si ...
- 转载爱哥自定义View系列--文字详解
FontMetrics FontMetrics意为字体测量,这么一说大家是不是瞬间感受到了这玩意的重要性?那这东西有什么用呢?我们通过源码追踪进去可以看到FontMetrics其实是Paint的一个内 ...
- 转载爱哥自定义View系列--Paint详解
上图是paint中的各种set方法 这些属性大多我们都可以见名知意,很好理解,即便如此,哥还是带大家过一遍逐个剖析其用法,其中会不定穿插各种绘图类比如Canvas.Xfermode.ColorFilt ...
随机推荐
- [转帖]Linux—编写shell脚本操作数据库执行sql
Linux-编写shell脚本操作数据库执行sql Hughman关注IP属地: 北京 0.0762020.03.20 09:02:13字数 295阅读 1,036 修改数据库数据 在升级应用时, ...
- 周末拾遗 xsos 的学习与使用
周末拾遗 xsos 的学习与使用 摘要 周末陪儿子上跆拳道课. 自己一个人傻乎乎的开着笔记本想着学习点东西. 上午看到了一个sosreport的工具. 本来想学习一下. 发现xsos 应该是更好的一个 ...
- [知乎]2019-nCov的致死率问题
https://www.zhihu.com/question/369630554/answer/998649507 知乎 dr.李的文章 跟自己一开始的理解很相似.. 个人也认为死亡率会高于2% 武汉 ...
- python+selenium+opencv验证滑块
我们在使用selenium爬虫的时候在登录时经常会遇到滑块验证码问题,导致登录受阻,正所谓万事开头难. 登录就登录不进去更别提往后的操作的.今天以登录京东后台来演示下如何破解滑块. 一.登录 首先我们 ...
- 分布式事务和Spanner分布式数据库
一.分布式事务 首先事务可以这么理解:程序员有一些不同的操作,或许针对数据库不同的记录,他们希望所有这些操作作为一个整体,不会因为失败而被分割,也不会被其他活动看到中间状态.事务处理系统要求程序员对这 ...
- Spring WebSocket实现实时通信的详细教程
简介 WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议.WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方. 我们 ...
- Postfix + Extmail 企业邮件服务器搭建
ExtMail套件用于提供从浏览器中登录.使用邮件系统的Web操作界面,而Extman套件用于提供从浏览器中管理邮件系统的Web操作界面.它以GPL版权释出,设计初衷是希望设计一个适应当前高速发展的I ...
- Python 实现指定窗口置顶激活
通过Python实现对特定窗口的置顶操作以及对特定窗体发送按键,这里需要安装一个第三方pip包,执行命令pywin32安装好以后,我们运行试试. 第一个案例,遍历所有句柄,然后对特定窗口进行最大化或最 ...
- 字节码编程,Javassist篇四《通过字节码插桩监控方法采集运行时入参出参和异常信息》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 字节码编程插桩这种技术常与 Javaagent 技术结合用在系统的非入侵监控中,这样 ...
- CSS背景设置与Emmet语法
CSS背景设置 通过CSS背景属性,可以给页面元素添加背景样式,页面元素指任意标签. 背景属性可以设置背景颜色,背景图片,背景平铺,背景图片位置,背景图像固定等. 背景颜色 一般默认值是:tran ...