一、简介

在上一篇文章中,我们介绍了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()方法,在上文中介绍过,ReentrantLockCondition结合,可以实现线程之间的等待/通知模型。

简单的示例,如下!

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

侧面也证明了一点,ReentrantLocksynchronized一样,锁都具有可重入特性,也就是说同一个线程多次调用同一个ReentrantLocklock()方法,可以再次进入方法体,无需阻塞等待。

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()方法,返回结果都是trueThread-1线程没有持有锁,因此isHeldByCurrentThread()方法返回falseisLocked()方法返回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常用方法详解的更多相关文章

  1. 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的无状态模 ...

  2. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  3. $.ajax()常用方法详解(推荐)

    AJAX 是一种与服务器交换数据的技术,可以在补充在整个页面的情况下更新网页的一部分.接下来通过本文给大家介绍ajax一些常用方法,大家有需要可以一起学习. 1.url: 要求为String类型的参数 ...

  4. 【Java】HashMap源码分析——常用方法详解

    上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...

  5. phpExcel常用方法详解【附有php导出excel加超级链接】

    phpExcel常用方法详解[附有php导出excel加超级链接] 发表于4年前(-- :) 阅读() | 评论() 0人收藏此文章, 我要收藏 赞0 http://www.codeplex.com/ ...

  6. Hexo系列(三) 常用命令详解

    Hexo 框架可以帮助我们快速创建一个属于自己的博客网站,熟悉 Hexo 框架提供的命令有利于我们管理博客 1.hexo init hexo init 命令用于初始化本地文件夹为网站的根目录 $ he ...

  7. phpExcel常用方法详解

    phpExcel常用方法详解[附有php导出excel加超级链接] 发表于4年前(2012-07-20 12:57) 阅读(510) | 评论(0) 0人收藏此文章, 我要收藏 赞0 http://w ...

  8. Signalr系列之虚拟目录详解与应用中的CDN加速实战

    目录 对SignalR不了解的人可以直接移步下面的目录 SignalR系列目录 前言 前段时间一直有人问我 在用SignalR 2.0开发客服系统[系列1:实现群发通讯]这篇文章中的"/Si ...

  9. 转载爱哥自定义View系列--文字详解

    FontMetrics FontMetrics意为字体测量,这么一说大家是不是瞬间感受到了这玩意的重要性?那这东西有什么用呢?我们通过源码追踪进去可以看到FontMetrics其实是Paint的一个内 ...

  10. 转载爱哥自定义View系列--Paint详解

    上图是paint中的各种set方法 这些属性大多我们都可以见名知意,很好理解,即便如此,哥还是带大家过一遍逐个剖析其用法,其中会不定穿插各种绘图类比如Canvas.Xfermode.ColorFilt ...

随机推荐

  1. [转帖]Linux—编写shell脚本操作数据库执行sql

    Linux-编写shell脚本操作数据库执行sql Hughman关注IP属地: 北京 0.0762020.03.20 09:02:13字数 295阅读 1,036 修改数据库数据   在升级应用时, ...

  2. 周末拾遗 xsos 的学习与使用

    周末拾遗 xsos 的学习与使用 摘要 周末陪儿子上跆拳道课. 自己一个人傻乎乎的开着笔记本想着学习点东西. 上午看到了一个sosreport的工具. 本来想学习一下. 发现xsos 应该是更好的一个 ...

  3. [知乎]2019-nCov的致死率问题

    https://www.zhihu.com/question/369630554/answer/998649507 知乎 dr.李的文章 跟自己一开始的理解很相似.. 个人也认为死亡率会高于2% 武汉 ...

  4. python+selenium+opencv验证滑块

    我们在使用selenium爬虫的时候在登录时经常会遇到滑块验证码问题,导致登录受阻,正所谓万事开头难. 登录就登录不进去更别提往后的操作的.今天以登录京东后台来演示下如何破解滑块. 一.登录 首先我们 ...

  5. 分布式事务和Spanner分布式数据库

    一.分布式事务 首先事务可以这么理解:程序员有一些不同的操作,或许针对数据库不同的记录,他们希望所有这些操作作为一个整体,不会因为失败而被分割,也不会被其他活动看到中间状态.事务处理系统要求程序员对这 ...

  6. Spring WebSocket实现实时通信的详细教程

    简介 WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议.WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方. 我们 ...

  7. Postfix + Extmail 企业邮件服务器搭建

    ExtMail套件用于提供从浏览器中登录.使用邮件系统的Web操作界面,而Extman套件用于提供从浏览器中管理邮件系统的Web操作界面.它以GPL版权释出,设计初衷是希望设计一个适应当前高速发展的I ...

  8. Python 实现指定窗口置顶激活

    通过Python实现对特定窗口的置顶操作以及对特定窗体发送按键,这里需要安装一个第三方pip包,执行命令pywin32安装好以后,我们运行试试. 第一个案例,遍历所有句柄,然后对特定窗口进行最大化或最 ...

  9. 字节码编程,Javassist篇四《通过字节码插桩监控方法采集运行时入参出参和异常信息》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 字节码编程插桩这种技术常与 Javaagent 技术结合用在系统的非入侵监控中,这样 ...

  10. CSS背景设置与Emmet语法

    CSS背景设置 通过CSS背景属性,可以给页面元素添加背景样式,页面元素指任意标签. 背景属性可以设置背景颜色,背景图片,背景平铺,背景图片位置,背景图像固定等.   背景颜色 一般默认值是:tran ...