点击 Mr.绵羊的知识星球 解锁更多优质文章。

目录

一、介绍

1. 简介

2. 是什么类型的锁

3. 优点

4. 原理

5. 主要方法

6. 使用时注意事项

二、实际应用

1. 案例一

2. 案例二


一、介绍

1. 简介

ReentrantLock是一种基于AQS(Abstract Queued Synchronizer)框架的应用实现,是JDK中一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

2. 是什么类型的锁

    (1) 公平锁或非公平锁(下面案例中有实际代码,自己执行一遍更容易理解)

    a. 公平锁:先来的线程先执行,排成排按顺序。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

b. 非公平锁:后来的线程有可能先执行,可插队不一定按顺序。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

    (2) 互斥锁

一次只能执行一个线程。

    (3) 可重入锁

同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

比较抽象,解释下:假如景区有3个收费项目,如果每个项目单独玩都需要收费。但是你买了个VIP。进入景区大门的时候工作人员为了方便,直接给你挂了个牌子,里面三个项目的工作人员看到你的牌子,就认为你已经买过该项目的门票,不在向你收费,进去就行。

3. 优点

    (1) 可中断并且可以设置超时时间。

    (2) 可以根据业务场景使用公平锁或非公平锁。

    (3) 获取锁可设置超时。

    (4) 可绑定多个条件(Condition)。

关于他的优点肯定是和其他锁比较得来的,一般都是和synchronized(synchronized介绍及使用 这篇文章稍后上传)比较。

4. 原理

ReentrantLock是怎么实现锁同步的呢?咱们先看代码:

点来点去发现,具体的加锁逻辑的实现在AQS这个抽象类中,所以想要了解其实现原理,还得看看AbstractQueuedSynchronizer(AQS详解 这篇文章稍后上传)

5. 主要方法

getHoldCount():当前线程调用 lock() 方法的次数。

getQueueLength():当前正在等待获取 Lock 锁的线程的估计数。

  getWaitQueueLength(Condition condition):当前正在等待状态的线程的估计数,需要传入 Condition 对象。

hasWaiters(Condition condition):查询是否有线程正在等待与 Lock 锁有关的 Condition 条件。

hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取 Lock 锁。

hasQueuedThreads():查询是否有线程正在等待获取此锁定。

isFair():判断当前 Lock 锁是不是公平锁。

isHeldByCurrentThread():查询当前线程是否保持此锁定。

  isLocked():查询此锁定是否由任意线程保持。

tryLock():线程尝试获取锁,如果获取成功,则返回 true,如果获取失败(即锁已被其他线程获取),则返回 false。

  tryLock(long timeout,TimeUnit unit):线程如果在指定等待时间内获得了锁,就返回true,否则返回 false。

lockInterruptibly():如果当前线程未被中断,则获取该锁定,如果已经被中断则出现异常。

方法详细介绍可以看最下面参考文章:ReentrantLock类中的方法

6. 使用时注意事项

(1) 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。

(2) 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。

说明:线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是A、B、C,否则可能出现死锁。

(3) 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。

a. 在lock方法与try代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。

b. 如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。

c. 在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。

d. 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。

说明:Lock对象的unlock方法在执行时,它会调用AQS的tryRelease方法(取决于具体实现类),如果当前线程不持有锁,则抛出IllegalMonitorStateException异常。

上面就是阿里规约中对锁的使用一些注意事项,感兴趣可以学习下!阿里巴巴编码规范学习及应用

二、实际应用

某个方法或者共享变量不能被多个线程同时操作,可以用ReentrantLock进行加锁。当然这只是其中的一种解决方式。

注意:使用之前你要知道,使用锁的话执行会相对较慢,因为在加锁的代码块内,每次只能执行一个线程。举个栗子(简单看看就行。git地址):

public static void main(String[] args) {// 设置执行次数int executeCount = 2;// 定义方法开始时间,单位: mslong startTime = System.currentTimeMillis();// java.util.concurrent提供的API,在该案例中主要是为了让主线程等待子线程结束后进行打印CountDownLatch countDownLatch = new CountDownLatch(executeCount);ReentrantLock lock = new ReentrantLock();for (int index = 0; index < executeCount; index++) {new Thread(() -> {LOGGER.info("current thread name: {} start.", Thread.currentThread().getName());// 加锁lock.lock();try {// 休眠1秒sleep(1);} finally {// 一定要在finally解锁lock.unlock();}/// 休眠1秒,如果测试不加锁耗时可将注释打开并对上面加锁逻辑进行注释// sleep(1);LOGGER.info("current thread name: {} end.", Thread.currentThread().getName());countDownLatch.countDown();}).start();}try {// 主线程等待子线程结束后打印log信息countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}LOGGER.info("cost time: {}ms", System.currentTimeMillis() - startTime);
}

通过上面的案例可知:加锁耗时2075ms,不加锁耗时1058ms。这有点耗时啊,所以如果必须加锁的话一定要把锁的范围控制在最小,避免浪费太多时间。但是这个加锁方式有个小问题,什么问题呢?我们继续往下看!

1. 案例一

(1) 场景:

王涛是公司的开发人员,他写了一个远程调用的方法,并且在调用这个方法前加了锁,调用之后解锁。他在本地测试没啥问题,所以就上线了。突然有一天很多客户点击王涛写的方法,结果发现一直加载中...没有任何提示。这线上问题一发生,搞的王涛和经理连夜加班找bug,之后进行如下修改。

(2) 代码:git地址

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;/*** ReentrantLockCase1* 使用ReentrantLock调用方法超时处理** @author wxy* @date 2023-02-16*/
public class ReentrantLockCase1 {private static final Logger LOGGER = LoggerFactory.getLogger(ReentrantLockCase1.class);public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();for (int index = 0; index < 2; index++) {new Thread(() -> {/*---修改前代码---*//*try {lock.lock();// 调用某个方法,这个方法会超时60s(偶现)timeoutApi();} finally {lock.unlock();}*//*---修改后代码---*/try {// 设置如果线程1正在调用,线程2等待5秒,5秒后你可以对线程2进行处理: 比如返回提示、线程处理...// 如果你不设置超时时间,那么所有的线程就会等待前一个线程解锁,具体怎么等待请看AQS详解// 备注: 正常情况下超时时间应该在配置文件中配置,可以按照业务随时进行调整if (lock.tryLock(5, TimeUnit.SECONDS)) {// 调用某个方法,这个方法会超时60s(偶现)timeoutApi();} else {/// 你可以写一些业务逻辑,来处理超时的线程2和超时期间的后续线程LOGGER.info("operation timeout");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 一定要在finally解锁lock.unlock();}}).start();}}/*** 超时的API*/private static void timeoutApi() {LOGGER.info("timeout api start.");sleep(60);LOGGER.info("timeout api end.");}/*** 设置超时时间** @param timeOut 超时时间(秒)*/private static void sleep(long timeOut) {try {TimeUnit.SECONDS.sleep(timeOut);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

当然,这是一种处理方式,还有其他的处理方式,欢迎评论区留言。

2. 案例二

(1) 场景

写了一下公平锁和非公平锁在执行过程中的打印。

(2) 代码:git地址

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.locks.ReentrantLock;/*** ReentrantLockCase2* 演示公平锁和非公平锁区别** @author wxy* @date 2023-02-16*/
public class ReentrantLockCase2 {private static final Logger LOGGER = LoggerFactory.getLogger(ReentrantLockCase1.class);public static void main(String[] args) {/*公平锁,执行结果:Thread-1, num : 1Thread-0, num : 1Thread-3, num : 1Thread-2, num : 1Thread-4, num : 1Thread-1, num : 2Thread-0, num : 2Thread-3, num : 2Thread-2, num : 2Thread-4, num : 25个线程很有规律,你一次我一次。*/ReentrantLock lock = new ReentrantLock(true);/*非公平锁,执行结果:Thread-0, num : 1Thread-0, num : 2Thread-1, num : 1Thread-1, num : 2Thread-2, num : 1Thread-2, num : 2Thread-3, num : 1Thread-3, num : 2Thread-4, num : 1Thread-4, num : 2一个线程执行完毕后,下一个线程才能开始执行。为什么出现这样的情况呢?非公平锁就是谁能抢谁先来,由于Thread-0线程刚执行完一次,线程还处于活跃状态,而其他线程需要被唤醒才能执行,所以相对于其他线程来说他更活跃,所以再次抢到锁并执行。*/// ReentrantLock lock = new ReentrantLock(false);for (int index = 0; index < 5; index++) {new Thread(() -> {for (int num = 1; num <= 2; num++) {lock.lock();try {LOGGER.info("thread name: {}, num : {}", Thread.currentThread().getName(), num);} finally {lock.unlock();}}}).start();}}
}

参考文章

1. ReentrantLock类中的方法

ReentrantLock介绍及使用(超详细)相关推荐

  1. JwtToken介绍与使用 超详细保姆级教程 内附详细示例代码

    文章目录 一.什么是JWT认证 二.JWT认证的特点 优点: 缺点: 三.JWT的组成 四.JWT代码展示 一.什么是JWT认证 Json web token (JWT),根据官网的定义,是为了在网络 ...

  2. 接口测试介绍以及实践超详细篇

    项目最近要测试接口,用到工具有postman,jmeter,YAPI(很推荐,可以实现自动化测试),这里就说下前面两位得力大将哈 首先,先了解下接口是啥 接口介绍 接口一般来说有两种:一种是程序内部的 ...

  3. 【云计算与大数据技术】Google、亚马逊、IBM、阿里云等云计算应用平台介绍讲解(超详细)

    云应用跟云计算最大的不同在于,云计算作为一种宏观技术发展概念而存在,而云应用则是直接面对客户解决实际问题的产品. "云应用"的工作原理是把传统软件"本地安装.本地运算&q ...

  4. k8s核心组件详细介绍教程(配超详细实例演示)

    本文实验环境基于上篇文章手把手从零开始搭建k8s集群超详细教程 本文根据B站课程云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps学习总结而来 k8s核心组件介绍 1 ...

  5. MyBatis超详细介绍——SQL语句构建器类

    MyBatis超详细介绍--SQL语句构建器类 (本文作为学习笔记,了解更多请参考:MyBatis参考文档) MyBatis3提供了SQL类帮助构造SQL语句: private String sele ...

  6. 超详细的jenkins持续集成工作原理、功能、部署方式等介绍

    文章由LinuxBoy分享于2019-08-30 11:08:39 超详细的jenkins持续集成工作原理.功能.部署方式等介绍 1. 概念 jenkins是一个开源项目,提供了一种易于使用的持续集成 ...

  7. 卷积神经网络超详细介绍

    文章目录 1.卷积神经网络的概念 2. 发展过程 3.如何利用CNN实现图像识别的任务 4.CNN的特征 5.CNN的求解 6.卷积神经网络注意事项 7.CNN发展综合介绍 8.LeNet-5结构分析 ...

  8. 卷积神经网络(CNN)超详细介绍

    文章转自:https://blog.csdn.net/jiaoyangwm/article/details/80011656 文章目录 1.卷积神经网络的概念 2. 发展过程 3.如何利用CNN实现图 ...

  9. 卷积神经网络超详细介绍1

    1.卷积神经网络的概念 2. 发展过程 3.如何利用CNN实现图像识别的任务 4.CNN的特征 5.CNN的求解 6.卷积神经网络注意事项 7.CNN发展综合介绍 8.LeNet-5结构分析 9.Al ...

最新文章

  1. centos升级mysql到5.5
  2. php处理json请求(php获取post请求的json数据的实现方法)
  3. 方法论、方法论——程序员的阿喀琉斯之踵
  4. jquery实现的3D缩略图悬停效果
  5. 更好也更快!最先进的图像去模糊算法DeblurGAN-v2
  6. 审计风险控制流程的起点_【经验分享】审计整改流程体系的优化措施
  7. vue 组件 父向子传值
  8. 【报告分享】2020快手电商生态报告.pdf(附下载链接)
  9. php上传图片文件常用的几个方法
  10. 用枚举法实现单例设计模型
  11. 学习 TList 类的实现[1]
  12. 深入浅出设计模式(十四):23种设计模式概念总结
  13. libjpeg-turbo 2.1.2 交叉编译
  14. 工具推荐:JDownloader - 下载网盘资料的好工具
  15. 关于topk accuracy
  16. 扇贝单词里有计算机英语吗,扇贝单词记录
  17. 《wireshark》怎么抓包
  18. 浅谈一键内存加速功能是如何实现的
  19. 博士毕业三年成为博导!他自述也曾想放弃科研
  20. ps 动画gif制作

热门文章

  1. mysql 中文分词搜索_php中文分词搜索
  2. node批量抓取并下载小姐姐照片
  3. BLDC驱动原理(不根据霍尔换相)
  4. js复选框之全选反选不选
  5. Downie 最靠谱易用的视频下载工具
  6. Python爬取淘宝商品信息保存到Excel
  7. 【应用笔记】AN1081_APM32F4xx_快速入门指南
  8. 【英语语法入门】第02讲 be动词的形式和用法
  9. 关于BQ40Z50-R1使用过程中的点点滴滴
  10. kuberSphere搭建中间件