Synchronized、Reetrantlock

news/2024/9/17 9:07:19/ 标签: java, redis, jvm

一、线程安全问题

多线程操作共享变量,由于该共享变量不是立刻可见的,读写不具备原子性,所以存在线程安全问题

二、售票案例

模拟售票案例,库存有10张票,有3个窗口(3个子线程)分别去卖,直到库存为0;

java">public class MyCount {public int ticket = 10;public void sell() {//调用多个线程去把ticket减到0for (int i = 0; i < 3; i++){new Thread(()->{while (true){if (ticket>0){//模拟售票耗费时间0.1秒try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;System.out.println(Thread.currentThread().getName()+"把ticket减到了"+ticket);}else {break;}}},"线程"+i).start();}}public static void main(String[] args) {MyCount myCount = new MyCount();myCount.sell();}
}

出现超卖和重卖的问题

在这里插入图片描述

三、Sychronized

3.1 修饰代码块

将同步代码块用sychronized(){ }标记。操作相同的共享变量的线程竞争的锁对象必须是同一个,放在小括号里。

java">    public void sell() {//调用多个线程去把ticket减到0for (int i = 0; i < 3; i++){new Thread(()->{while (true){// 加synchronizedsynchronized (MyCount.class){if (ticket>0){//模拟售票耗费时间0.1秒try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;System.out.println(Thread.currentThread().getName()+"把ticket减到了"+ticket);}else {break;}}}},"线程"+i).start();}}

在这里插入图片描述

修改synchronized的锁对象,发现锁失效了。
原因是每个线程里都有各自的obj对象,不是同一个。

在这里插入图片描述

在这里插入图片描述

3.2 修饰方法

注意:这里多个Thread使用的是同一个实现了Runnable接口的类的对象

java">public class MyCount {public void sell() {//调用多个线程去把ticket减到0Window w = new Window();for (int i = 0; i < 3; i++){new Thread(w,"线程"+i).start();}}public static void main(String[] args) {MyCount myCount = new MyCount();myCount.sell();}
}class Window implements Runnable{private int ticket = 10;@Overridepublic void run() {while (true){this.sell();}}synchronized void sell(){if (ticket>0){//模拟售票耗费时间0.1秒try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;System.out.println(Thread.currentThread().getName()+"把ticket减到了"+ticket);}}
}

在这里插入图片描述

3.3 synchronized总结

  • 任意对象都可以作为同步锁。
  • 同步方法的锁:静态方法(类名.class)、非静态方法(this)
  • 同步代码块:自己指定,很多时候也是指定为this或类名.class

四、Reentrantlock

4.2 使用流程

使用流程

  1. 实例化 ReentrantLock
  2. 调用锁定方法lock()
  3. 调用解锁方法unlock()

在这里插入图片描述
如果同步代码块会有异常,要把unlock()写到finally

java">import java.util.concurrent.locks.ReentrantLock;public class MyCount {public int ticket = 10;private final ReentrantLock lock = new ReentrantLock();public void sell() {//调用多个线程去把ticket减到0for (int i = 0; i < 3; i++){new Thread(()->{while (true){//获取锁lock.lock();//需要保证线程安全的代码try{if (ticket>0){//模拟售票耗费时间0.1秒try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;System.out.println(Thread.currentThread().getName()+"把ticket减到了"+ticket);}else {break;}}finally {//释放锁lock.unlock();}}},"线程"+i).start();}}public static void main(String[] args) {MyCount myCount = new MyCount();myCount.sell();}
}

默认是非公平锁,也就是后来的线程也可能抢到锁。
在这里插入图片描述

4.2 公平锁

公平锁:线程会排队,排在前面的先获得锁。如果这个锁是公平锁,那么线程来的时候会检查队列里是否有别的线程在排队,如果有的话就进队列里让别的线程先运行。

java">private final ReentrantLock lock = new ReentrantLock(true);

在这里插入图片描述

4.3 尝试获取锁tryLock()

  • 不管锁有没有获取到,都会继续向下执行,而不会阻塞。
  • 有返回值,返回是否成功获取到了锁
java">    public void sell() {//调用多个线程去把ticket减到0for (int i = 0; i < 3; i++){new Thread(()->{while (true){//获取锁boolean tried = lock.tryLock();System.out.println(Thread.currentThread().getName()+"尝试获取锁:"+tried);//需要保证线程安全的代码try{if (ticket>0){//模拟售票耗费时间0.1秒try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;System.out.println(Thread.currentThread().getName()+"把ticket减到了"+ticket);}else {break;}}finally {//释放锁lock.unlock();}}},"线程"+i).start();}}

这里抛出异常是因为,没有获取到锁但是却有调用了unlock()

在这里插入图片描述
所以需要在unlock之前判断一下是否获取到了锁

java">    public void sell() {//调用多个线程去把ticket减到0for (int i = 0; i < 3; i++){new Thread(()->{while (true){//获取锁boolean tried = lock.tryLock();//需要保证线程安全的代码try{//如果获取成功if (tried){if (ticket>0){//模拟售票耗费时间0.1秒try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket--;System.out.println(Thread.currentThread().getName()+"把ticket减到了"+ticket);}else {break;}}}finally {//释放锁if (tried){lock.unlock();}}}},"线程"+i).start();}}

在这里插入图片描述

4.3 lockInterrruptibly()可中断地获取锁

正解:当t1执行时间很长,t2(获取锁时采用lockInterrruptibly()方式)一直在等待获取锁,一直获取不到,这时候t2可以被别的线程中断。
如果t2是lock方式获取锁,那他只能陷入无休止的等待获取锁的过程中,不能直接被中断

这是错误的:线程t1调用lockInterrruptibly()方法获取锁后,别的线程可以立刻打断t1的执行

java">    public void sell() throws InterruptedException {Thread t1 = new Thread(() -> {try {lock.lock();System.out.println("t1 start");Thread.sleep(Long.MAX_VALUE);System.out.println("t1 end");} catch (InterruptedException e) {System.out.println("t1 被中断");} finally {lock.unlock();}});t1.start();// 阻塞1秒,保证上面的线程先执行,先获取到锁Thread.sleep(1000);Thread t2 = new Thread(() -> {try {lock.lockInterruptibly();System.out.println("t2 start");Thread.sleep(Long.MAX_VALUE);System.out.println("t2 end");} catch (InterruptedException e) {System.out.println("t2 被中断");} finally {//lock.unlock();}});t2.start();Thread.sleep(3000);t2.interrupt();}

在这里插入图片描述
将lock.lockInterruptibly()改为lock.lock()后

在这里插入图片描述


http://www.ppmy.cn/news/1521574.html

相关文章

论文速读|重新审视奖励设计与评估:用于强健人型机器人站立与行走控制的方法

论文地址&#xff1a;https://arxiv.org/pdf/2404.19173 这篇论文为类人机器人站立和行走&#xff08;SaW&#xff09;控制器的持续可衡量改进奠定了基础。通过引入一套定量实际基准测试方法&#xff0c;作者展示了现有控制器的优缺点&#xff0c;并通过基准测试指导新控制器的…

【Go 快速入门】Go Test 工具 | 单元测试 | 基准测试

文章目录 go test工具单元测试子测试测试覆盖率 基准测试性能比较函数重置时间并行测试 本节项目地址&#xff1a;07-UnitTestBenchmarkTest go test工具 Go语言中的测试依赖go test&#xff0c;该命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内&#xff0c;…

QT实现简易记事本

QT的Qmainwindow实现简易记事本,不带有UI界面。 1、功能展示 1、编辑文件时&#xff0c;文件名左上角有个“*”,代表文件未保存 2、菜单栏有文件、编辑的选项 3、工具栏有保存、新建、打开、调节字体、调节颜色。 4、文件菜单中有&#xff0c;新建&#xff0c;保存、打开、…

构建私有CA和证书

一、准备 两台虚拟机 192.168.252.148 CA 192.168.252.149 客户端 二、构建私有CA 192.168.252.148 CA 安装openssl 1.检查是否存在 rpm -qa openssl 2.安装或者更新openssl yum install openssl openssl-devel -y 查看CA相关配置 /etc/pki/tls/openssl.cnf这个文件是…

Golang | Leetcode Golang题解之第391题完美矩形

题目&#xff1a; 题解&#xff1a; func isRectangleCover(rectangles [][]int) bool {type point struct{ x, y int }area, minX, minY, maxX, maxY : 0, rectangles[0][0], rectangles[0][1], rectangles[0][2], rectangles[0][3]cnt : map[point]int{}for _, rect : range…

基于Vector Davinci SIP的标定实现方法

标定是什么 在汽车软件开发过程中&#xff0c;软件做好之后一般都需要在实车上进行调试&#xff0c;尤其是底盘领域&#xff0c;软件的性能与实车参数、状态有很大的关系&#xff0c;很多参数在开发阶段没办法直接确定最优值&#xff0c;只能是先定一个大概的默认值&#xff0…

maven-helper插件解决jar包冲突实战

经常遇到jar包冲突问题&#xff0c;今天梳理一下&#xff1a; 1、打开idea 2、安装后 打开pom文件 点击 3、 4、 5、 6、 7、 8、 9、 可参考的类似文章

Day 8:77 组合

77 组合 1. 题目描述2. 解题思路3. 代码实现4. 回溯模板 1. 题目描述 77 组合 2. 解题思路 该题可以使用回溯类型的模板来解决&#xff0c;注意到可以进行剪枝操作。 3. 代码实现 class Solution {vector<vector<int>> res;vector<int> path; public:ve…

nodejs发邮件如何实现自动化邮件发送功能?

nodejs发邮件的安全性探讨&#xff1f;使用nodejs发邮件的教程&#xff1f; 在处理邮件发送这类重复性任务时&#xff0c;使用Node.js发邮件的功能可以极大地简化工作流程。AokSend将详细探讨如何利用Node.js发邮件来实现自动化邮件发送功能&#xff0c;确保您的邮件发送任务既…

双指针法01

习题1 27. 移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下…

2024高教社杯全国大学生数学建模竞赛C题原创python代码

2024高教社杯全国大学生数学建模竞赛C题原创python代码 C题题目&#xff1a;农作物的种植策略 思路可以参考我主页之前的文章 以下均为python代码&#xff0c;推荐用anaconda中的notebook当作编译环境 from gurobipy import Model import pandas as pd import gurobipy as g…

探索TinyDB:轻量级数据库的优雅之旅

文章目录 探索TinyDB&#xff1a;轻量级数据库的优雅之旅背景&#xff1a;为何选择TinyDB&#xff1f;TinyDB是什么&#xff1f;如何安装TinyDB&#xff1f;简单库函数使用方法场景应用常见Bug及解决方案总结 探索TinyDB&#xff1a;轻量级数据库的优雅之旅 背景&#xff1a;为…

记一次MES项目上线失败总结--数据库层面+代码层面优化方案

难熬三个夜晚&#xff01;&#xff01;&#xff01;按原计划将ERP的生产订单、排程单、牌卡计划、小包装、装箱单等生产数据通过接口的形式同步到MES系统&#xff0c;其中生产订单、排程单和小包装、装箱单的数据量相对较少&#xff0c;合计大概50w条左右的数据&#xff0c;同步…

免费开源的低代码表单FormCreate安装教程,支持可视化设计,适配移动端

低代码表单FormCreate 是一个可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的表单生成组件。它支持 6 个 UI 框架&#xff0c;适配移动端&#xff0c;并且支持生成任何 Vue 组件。内置 20 种常用表单组件和自定义组件&#xff0c;再复杂的表单都可以轻松搞定 源码…

Laravel 中间件与事件应用教程

前言 在 Laravel 框架中&#xff0c;中间件&#xff08;Middleware&#xff09;和事件&#xff08;Events&#xff09;是两种强大的机制&#xff0c;用于处理 HTTP 请求和应用程序中的特定动作。它们各自有独特的应用场景和优势。本教程将详细介绍中间件和事件的基本概念、区别…

TriforceAFL部署、使用与原理分析

文章目录 前言1、概述1.1、工作原理1.2、工作流程1.2.1、编译TriforceAFL1.2.2、编译TriforceLinuxSyscallFuzzer1.2.3、初始化资源1.2.3.1、种子初始化1.2.3.2、GuestOS初始化 1.2.4、afl-fuzz调度1.2.5、启动待Fuzz目标 2、安装与使用2.1、安装方法2.1.1、部署系统依赖组件2.…

​如何通过Kimi强化论文写作中的数据分析?

在学术研究领域&#xff0c;数据分析是验证假设、发现新知识和撰写高质量论文的关键环节。Kimi&#xff0c;作为一款先进的人工智能助手&#xff0c;能够在整个论文写作过程中提供支持&#xff0c;从文献综述到数据分析&#xff0c;再到最终的论文修订。本文将详细介绍如何将Ki…

11 Java 方法引用、异常处理、Java接口之函数式编程(接口知识补充Function<T,R>、BiFunction<T, U, R>和自定义泛型接口)

文章目录 前言一、Java接口之函数式编程 --- 接口知识补充1 Function<T,R>泛型接口2 BiFunction<T, U, R>泛型接口3 自定义泛型函数式编程接口4 使用lambda表达式、方法引用进行函数式编程二、方法引用1 方法引用初体验(以Array.sort()方法为例)(1)什么是方法引…

GNU/Linux - 进程关联的控制终端

Controlling terminal 在类 Unix 操作系统中&#xff0c;控制终端是与进程组相关联的终端设备&#xff0c;它控制着进程与终端输入和输出的交互。控制终端通常是启动进程的终端。它负责处理进程的输入&#xff08;如键盘输入&#xff09;和输出&#xff08;如显示文本&#xff…

应用方案 | D6299B马达驱动电路

概述&#xff1a; D6299B是一款功能强大的马达驱动芯片&#xff0c;专为各种应用场景设计。其宽工作电压范围、微小待机电流以及多重保护功能&#xff0c;使其在众多马达驱动芯片中脱颖而出。以下是对D6299B的详细概述。 首先&#xff0c;D6299B具有3-25…