java ReentrantLock 锁 await、signal的用法

背景

在并发编程中,为了保证线程的原子执行,需要使用锁,jvm 内 可以使用 synchronized 和 ReentrantLock,如果是集群部署,我们可以使用Redis 分布式锁 其他的锁后面再介绍。

ReentrantLock 和 synchronized

1、ReentrantLock 通过方法 lock()与 unlock()来进行加锁与解锁操作,与synchronized(1.8之后性能得到提升)会被JVM自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁

2、ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁

简述

ReentrantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完
成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等
避免多线程死锁的方法

Lock 在 java.util.concurrent.locks

ReentrantLock 实现了 Lock 接口,拥有下面几个方法:

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;public interface Lock {//执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经
被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁void lock();//如果当前线程未被中断,获取锁void lockInterruptibly() throws InterruptedException;//如果锁可用, 则获取锁, 并立即返回 true, 否则返回 falseboolean tryLock();//如果锁在给定等待时间内没有被另一个线程保持,则获取该锁并返回 true,否则返回 false。boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程
并不持有锁, 却执行该方法, 可能导致异常的发生void unlock();//条件对象,获取等待通知组件Condition newCondition();
}

初始化 锁

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockTest {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();
}

说明

1、同一时刻只有一个线程能获取独占锁(lock.lock()),持有了这个锁的人才能调用condition.await()和 condition.sign()方法。


2、调用 condition.await()方法会释放独占锁(lock.unlock),并且将自身加入到condition队列中。


3、Thread-signal-1线程获取lock 后,condition.signal()方法不会立即唤醒await中的线程,而是将condition队列中的线程转移到AQS队列中。


4、当持锁线程释放锁时,AQS队列的线程再抢到则会被唤醒。

线程池中使用锁

1、我们使用线程池 来演示 如何使用 ReentrantLock,首先创建一个 固定的线程池

ExecutorService executor = Executors.newFixedThreadPool(10);

2、发起10个线程的并发处理,这里使用for 循环来提交 线程到线程池

3、每个线程 实现 Runnable 接口的run 方法

4、run方法中尝试获取锁,等待2秒,获取到锁后,休眠10秒,然后调用await 方法,在调用await方法时,当前线程会把自身加入到condition中,同时释放 lock 锁(当然这里也有部分线程获取不到锁)。

5、run 方法执行完成后,本次请求结束。

6、请求unlock路由,这时线程会去尝试获取 lock , 然后 调用 condition.signal() 方法 来唤起刚刚通过 condition.await() 加入到condition 队列中的线程,当lock.unlock() 后,会执行刚刚加入condition队列中线程的剩余代码并打印 "signal-> await 执行剩余的代码:pool-4-thread-1"。

package com.yd.controller.user;import com.yd.entity.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@RestController
@RequestMapping("/admin/user")
public class LockTest {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();@GetMapping("exec")public void execs() {ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executor.execute(new Runnable() {@Overridepublic void run() {try {if (lock.tryLock(2, TimeUnit.SECONDS)) {System.out.println("获取到锁:" + Thread.currentThread().getName());TimeUnit.SECONDS.sleep(10);condition.await(); //此处会将当前线程放到队列condition,并释放lock锁,其余线程才能获取到锁,但下面的代码不会被执行,只有当signal() 方法被调用时,才会打印下面的代码System.out.println("signal-> await 执行剩余的代码:" + Thread.currentThread().getName());lock.unlock();} else {System.out.println("未获取锁" + Thread.currentThread().getName());}} catch (InterruptedException e) {throw new RuntimeException(e);}}});}executor.shutdown();}@GetMapping("unlock")public ResponseEntity<String>  unlock() {lock.lock();condition.signalAll();lock.unlock();return ResponseEntity.success("yes");}
}

启动springboot 后,访问

curl -X GET http://127.0.0.1:8089/admin/user/exec

执行结果如下,只有 thread-1 获取到锁,其余线程均未获取到锁

获取到锁:pool-4-thread-1
未获取锁pool-4-thread-2
未获取锁pool-4-thread-7
未获取锁pool-4-thread-4
未获取锁pool-4-thread-5
未获取锁pool-4-thread-6
未获取锁pool-4-thread-10
未获取锁pool-4-thread-3
未获取锁pool-4-thread-8
未获取锁pool-4-thread-9

接下来访问

curl http://127.0.0.1:8089/admin/user/unlock

执行结果如下,会将上面 thread-1 加入到condition 队列中的剩余代码执行完毕。

signal-> await 执行剩余的代码:pool-4-thread-1

注意事项

在单元测试中,想要看到锁的效果,需要在代码后面加一个 睡眠提醒(Thread.sleep(10000),主线程结束后,锁的运行逻辑表现不出来。

在springboot中,主线程默认是后台运行的,没有影响。

Semaphore 信号量

       

Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信
号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore 可以用来
构建一些对象池,资源池之类的,比如数据库连接池。

Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似,通过 acquire()与
release()方法来获得和释放临界资源。

在springboot中使用单元测试,直接上代码:

 @Testpublic void testSem() throws InterruptedException {Semaphore semaphore = new Semaphore(5);ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executor.execute(new Runnable() {@Overridepublic void run() {try {semaphore.acquire();Thread.sleep(4000);//DateFormatUtils 使用时,需要引入common-lang3 包System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + "获取到锁");} catch (InterruptedException e) {throw new RuntimeException(e);} finally {semaphore.release();}}});}executor.shutdown();//让主线程挂起,否则程序直接退出Thread.sleep(20000);}

执行结果如下, 开启10个线程来执行,每次最多5个线程获取到锁

2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁


2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/112546.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入浅出AXI协议(3)——握手过程

一、前言 在之前的文章中我们快速地浏览了一下AXI4协议中的接口信号&#xff0c;对此我们建议先有一个简单的认知&#xff0c;接下来在使用到的时候我们还会对各种信号进行一个详细的讲解&#xff0c;在这篇文章中我们将讲述AXI协议的握手协议。 二、握手协议概述 在前面的文章…

nowcoder NC236题 最大差值

目录 题目描述&#xff1a; 示例1 示例2 题干解析&#xff1a; 暴力求解&#xff1a; 代码展示&#xff1a; 优化&#xff1a; 代码展示&#xff1a; 题目跳转https://www.nowcoder.com/practice/a01abbdc52ba4d5f8777fb5dae91b204?tpId128&tqId33768&ru/exa…

云南森林火灾vr消防模拟安全演练系统训练消防员火灾和事故的适应和应对能力

据统计,每一场破坏性地震发生后,会引发次生的灾害,而火灾是其中之一。导致火灾的原因,推测是地震时使供电线路短路,引燃易燃物,火灾就随即发生。所以,在日常生活中,定期的消防演练还是非常必要的, VR消防&#xff0c;是VR公司深圳华锐视点利用VR虚拟现实技术&#xff0c;将VR和…

汽车摩托车零部件出口管理ERP解决方案

近年来&#xff0c;随着全球经济的发展&#xff0c;人们对交通工具的需求增加&#xff0c;国内汽车、摩托车市场的不断扩大&#xff0c;以及国内制造技术的不断提高&#xff0c;中国汽车、摩托车零部件出口业务迎来了广阔的发展前景&#xff0c;带动了汽车配件和摩托车配件市场…

java企业工程项目管理系统源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff…

postman-使用Postman的模拟服务来模拟(mock)后端数据,完成前端模拟API调用

最近项目上比较忙&#xff0c;任务多时间紧&#xff0c;导致后端开发任务繁多&#xff0c;无法及时开发完毕&#xff0c;但是前端同学已经把对应功能开发完成&#xff0c;需要进行前后端联调来验证API及一些交互问题&#xff1b;这不能因为后端的进度来影响前端的工作完成情况&…

uniapp返回上一页并刷新

在uniapp中&#xff0c;经常会有返回上一页的情况&#xff0c;官方提供有 uni.navigateBack 这个api来实现效果&#xff0c;但是此方法返回到上一页之后页面并不会更新&#xff08;刷新&#xff09;。 例如有这样一个场景&#xff1a;从地址列表页点击添加按钮进入添加地址页面…

【大模型】基于 LlaMA2 的高 star 的 GitHub 开源项目汇总

【大模型】基于 LlaMA2 的高 star 的 GitHub 开源项目汇总 Llama2 简介开源项目汇总NO1. FlagAlpha/Llama2-ChineseNO2. hiyouga/LLaMA-Efficient-TuningNO3. yangjianxin1/FireflyNO4. LinkSoul-AI/Chinese-Llama-2-7bNO5. wenge-research/YaYiNO6. michael-wzhu/Chinese-LlaM…

vue2 组件库之vetur提示

当我们开发完自定义UI组件库后&#xff0c;在项目中使用时&#xff0c;想要达到以下提示效果&#xff0c;组件提示与属性提示&#xff0c;有什么解决方案呢&#xff1a; 事实上&#xff0c;这是vetur的功能&#xff0c;原文如下&#xff1a; Component Data | Vetur If a pac…

器件手册识读之 :运放

器件手册识读之 &#xff1a;运放 一、基本信息 二、引脚排列 三、最大额定参数 四、电气特性 五、应用电路 1、称重传感器放大器 2、热电偶低偏置&#xff0c;低漂移环路测量二极管冷端补偿。

MySQL 保存日期用哪种数据类型

写在前面 在设计数据库表时不可避免的需要用到时间类型&#xff0c;到底选择那种数据类型来表示时间是一个值的讨论的问题&#xff0c;本文就一起来看下&#xff01; 1&#xff1a;能用哪些数据类型 1:字符串&#xff1a;不要用,占用空间大&#xff0c;至少需要19个字节&…

Qt应用开发(基础篇)——字体选择器 QFontDialog

一、前言 QFontDialog类继承于QDialog&#xff0c;是一个设计用来选择字体的对话框部件。 对话框窗口QDialog QFontDialog字体选择对话框&#xff0c;设计用来让用户选择某一种字体&#xff0c;一般用于文本编辑窗口、标签显示和一些需要文本输入的场景。你可以直接使用静态函数…

Docker笔记

学习了神光大佬的《Nest 通关秘籍》后&#xff0c;对docker做了个笔记&#xff0c;并实操部署了一下个人项目&#xff0c;在此记录一下 是什么 Docker是一种开源的容器化平台&#xff0c;它可以将应用程序及其依赖项打包到一个可移植的容器中&#xff0c;使得应用程序能够在任…

Kubernetes(七)修改 pod 网络(flannel 插件)

一、 提示 需要重启服务器 操作之前备份 k8s 中所有资源的 yaml 文件 如下是备份脚本&#xff0c;仅供参考 # 创建备份目录 test -d $3 || mkdir $3 # $1 命名空间 # $2 资源名称&#xff1a; sts deploy configMap svc 等 # $3 资源备份存放的目录名称for app in kubec…

MySQL DATE_SUB的实践

函数简介DATE_SUB()函数从DATE或DATETIME值中减去时间值(或间隔)。 下面说明了DATE_SUB()函数的语法&#xff1a; DATE_SUB(start_date,INTERVAL expr unit); DATE_SUB()函数接受两个参数&#xff1a; start_date是DATE或DATETIME的起始值。 expr是一个字符串&#xff0c;用于确…

接口幂等性设计的最佳实现

一、什么是幂等 二、为什么需要幂等 三、接口超时了&#xff0c;到底如何处理&#xff1f; 四、如何设计幂等 全局的唯一性ID 幂等设计的基本流程 五、实现幂等的8种方案 selectinsert主键/唯一索引冲突 直接insert 主键/唯一索引冲突 状态机幂等 抽取防重表 token令牌 悲观锁…

【Jetpack】Navigation 导航组件 ⑤ ( NavigationUI 类使用 )

文章目录 一、NavigationUI 类简介二、NavigationUI 类使用流程1、创建 Fragment2、创建 NavigationGraph3、Activity 导入 NavHostFragment4、创建菜单5、Activity 界面开发 NavigationUI 的主要逻辑 ( 重点 )a、添加 Fragment 布局b、处理 Navigation 导航逻辑 ( 重点 )c、启…

MacOS goland go1.21 debug问题

安装dlv brew install dlv 安装之后在终端会显示所在目录 类似/usr/local/Cellar/delve/1.21.0/bin 配置goland 在文件系统中找到goland 右击选择show package contents -> Contents -> plugins -> go 尝试替换 其中对应系统 的 dlv 结果还是不行 然后打开应用gol…

Huggingface托管机器学习模型及API提供

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 我想在我的网络和移动应用程序中使用机器学习模型&#xff0c;但要做到这一点&#xff0c;我必须在某个地方托管我的机器学习应用程序。 托管预先训练的 ML 模型称为推理。 我只想添加一些 Python ML 代码并快速获得 REST…

【拾枝杂谈】从游戏开发的角度来谈谈原神4.0更新

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;结合最近的学习内容和以后自己的目标&#xff0c;今天又开了杂谈这个新坑&#xff0c;分享一下我在学习游戏开发的成长和自己的游戏理解&#xff0c;当然现在还是一枚…