JAVA多线程(5)

JAVA多线程(5)

线程安全问题概述

卖票问题分析

  • 单窗口卖票

img

一个窗口(单线程)卖100张票没有问题
单线程程序是不会出现线程安全问题的

  • 多个窗口卖不同的票

img

3个窗口一起卖票,卖的票不同,也不会出现问题
多线程程序,没有访问共享数据,不会产生问题

  • 多个窗口卖相同的票

img

3个窗口卖的票是一样的,就会出现安全问题
多线程访问了共享的数据,会产生线程安全问题

线程安全问题代码实现

模拟卖票案例

创建3个线程,同时开启,对共享的票进行出售

public class Demo01Ticket {public static void main(String[] args) {//创建Runnable接口的实现类对象RunnableImpl run = new RunnableImpl();//创建Thread类对象,构造方法中传递Runnable接口的实现类对象Thread t0 = new Thread(run);Thread t1 = new Thread(run);Thread t2 = new Thread(run);//调用start方法开启多线程t0.start();t1.start();t2.start();}
}
public class RunnableImpl implements Runnable{//定义一个多个线程共享的票源private int ticket = 100;//设置线程任务:卖票@Overridepublic void run() {//使用死循环,让卖票操作重复执行while(true){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}}
}

线程安全问题原理分析

img

线程安全问题产生原理图

分析:线程安全问题正常是不允许产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权;让其他的线程只能等待,等待当前线程卖完票,其他线程在进行卖票。

解决线程安全问题办法1-synchronized同步代码块

同步代码块:synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

使用synchronized同步代码块格式:

synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

代码实现如下:

public class Demo01Ticket {public static void main(String[] args) {//创建Runnable接口的实现类对象RunnableImpl run = new RunnableImpl();//创建Thread类对象,构造方法中传递Runnable接口的实现类对象Thread t0 = new Thread(run);Thread t1 = new Thread(run);Thread t2 = new Thread(run);//调用start方法开启多线程t0.start();t1.start();t2.start();}
}
public class RunnableImpl implements Runnable{//定义一个多个线程共享的票源private int ticket = 100;//创建一个锁对象Object obj = new Object();//设置线程任务:卖票@Overridepublic void run() {//使用死循环,让卖票操作重复执行while(true){//同步代码块synchronized (obj){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}}}
}

⚠️注意:

  1. 代码块中的锁对象,可以使用任意的对象。
  2. 但是必须保证多个线程使用的锁对象是同一个。
  3. 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行。

同步技术原理分析

同步技术原理:

使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器

3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票。

  • t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块这时t0会检查synchronized代码块是否有锁对象

发现有,就会获取到锁对象,进入到同步中执行

  • t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块这时t1会检查synchronized代码块是否有锁对象

发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象,t0线程执行完同步中的代码,会把锁对象归 还给同步代码块t1才能获取到锁对象进入到同步中执行

总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。

解决线程安全问题办法2-synchronized普通同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:

public synchronized void payTicket(){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

代码实现:

 public /**synchronized*/ void payTicket(){synchronized (this){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}}

分析:

定义一个同步方法,同步方法也会把方法内部的代码锁住,只让一个线程执行。

同步方法的锁对象是谁?

就是实现类对象 new RunnableImpl(),也是就是this,所以同步方法是锁定的this对象。

解决线程安全问题办法3-synchronized静态同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
格式:

public static synchronized void payTicket(){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

代码实现:

 public static /**synchronized*/ void payTicketStatic(){synchronized (RunnableImpl.class){//先判断票是否存在if(ticket>0){//提高安全问题出现的概率,让程序睡眠try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");ticket--;}}}

分析:

静态的同步方法锁对象是谁?

不能是this,this是创建对象之后产生的,静态方法优先于对象

静态方法的锁对象是本类的class属性–>class文件对象(反射)。

解决线程安全问题办法4-Lock锁

Lock接口中的方法:

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁

使用步骤:

  1. 在成员位置创建一个ReentrantLock对象
  2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

代码实现:

public class RunnableImpl implements Runnable{//定义一个多个线程共享的票源private int ticket = 100;//1.在成员位置创建一个ReentrantLock对象Lock l = new ReentrantLock();//设置线程任务:卖票@Overridepublic void run() {//使用死循环,让卖票操作重复执行while(true){//2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁l.lock();try {//先判断票是否存在if(ticket>0) {//提高安全问题出现的概率,让程序睡眠Thread.sleep(10);//票存在,卖票 ticket--System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");ticket--;}} catch (InterruptedException e) {e.printStackTrace();}finally {l.unlock();//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁//无论程序是否异常,都会把锁释放}}}

分析

java.util.concurrent.locks.Lock接口

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。

2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

公平锁、非公平锁的创建方式:

//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);

3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

ReentrantLock和Synchronized的区别

相同点:

  1. 它们都是加锁方式同步;
  2. 都是重入锁;
  3. 阻塞式的同步;也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善);

不同点:

ck对象可以同时绑定多个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

ReentrantLock和Synchronized的区别

相同点:

  1. 它们都是加锁方式同步;
  2. 都是重入锁;
  3. 阻塞式的同步;也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善);

不同点:

img

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

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

相关文章

【Go入门】 Go如何使得Web工作

【Go入门】 Go如何使得Web工作 前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的Web工作方式。 w…

nginx 如何根据IP做限流,以及 nginx 直接返回 json 格式数据

Nginx 限流配置 Nginx是如何限流的。随着业务的扩散,系统并发越来越高时,有三样利器用来保护系统,分别是缓存、降级和限流。 随着业务的扩散,系统并发越来越高时,有三样利器用来保护系统,分别是缓存、降…

竞赛选题 深度学习花卉识别 - python 机器视觉 opencv

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 &a…

Canal+Kafka实现MySQL与Redis数据同步(一)

CanalKafka实现MySQL与Redis数据同步(一) 前言 在很多业务情况下,我们都会在系统中加入redis缓存做查询优化。 如果数据库数据发生更新,这时候就需要在业务代码中写一段同步更新redis的代码。 这种数据同步的代码跟业务代码糅合…

公司内部网络架设悟空CRM客户管理系统 cpolar无需公网IP实现内网,映射端口外网访问

1、什么是内网穿透? 内网穿透,即内网映射,内网IP端口映射到外网的过程。是一种主动的操作,需要本人一些内网的权限。比如在公司自己电脑,将办公OA发布到互联网,然后提供外网在家或出差在外连接访问。 可以…

本地开发环境和服务器传输数据的几种方法

❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️ 👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…

pyecharts 绘制轮播效果图例子

pyecharts 绘制轮播效果图例子 例子1例子2例子3代码解析例子1 from pyecharts.charts import Timeline,Bar timeline=Timeline() data=[{"name":"2025","value"

【洛谷 B2002】Hello,World!(顺序结构)

Hello,World! 题目描述 编写一个能够输出 Hello,World! 的程序。 提示: 使用英文标点符号;Hello,World! 逗号后面没有空格。H 和 W 为大写字母。 输入格式 输出格式 样例 #1 样例输入 #1 无样例输出 #1 Hello,World!思路 #include 是一个预处…

go语言window|mac|linux下交叉编译其他平台的软件包

go语言window|mac|linux下交叉编译其他平台的软件包 注意:go1.17 发现直接使用set 是不起作用的, 必须要使用go env -w 来设置 一、设置编译环境,需要发布哪个平台 Window下 1、 设置Linux编译环境 SET CGO_ENABLED0 set GOARCHamd64 se…

2023-11-19 思考-打造个人品牌-活动-分析和反思

摘要: 下午参加了一个打造个人品牌的活动, 主旨自然是关于个人品牌的塑造和展示. 除了演讲传播的知识外,在听的过程中, 引发了我的一些不同角度的思考. 本文记录相关过程及思考并作分析. 活动内容: 一. 当前自己的状态与希望未来达到的状态 我写的是, 当前我是个技术专家, 整…

隐式转换导致索引失效的原因

Num1 int Num2 varchar Str1不能为null Str2可null 例子1: 结果:124非常快,0.001~0.005秒出结果。3最慢,4~5秒出结果。 查询执行计划:124索引扫描。3全表扫描。 解释:首先四个23都产生隐式转换&#x…

使用 `spring-statemachine-redis` 实现状态机

使用 spring-statemachine-redis 实现状态机主要涉及到以下几个步骤: 添加依赖:首先,你需要在你的 pom.xml(对于 Maven 项目)或 build.gradle(对于 Gradle 项目)中添加 Spring State Machine 和…

计蒜客T1443 看灯笼(C语言实现)

【题目描述】花椰妹喜欢看灯笼。有一条铁路,长度为L-1,最左端为1号点,最右端为L号点。每两个相邻点距离为1。现在有的单位点上挂着灯笼,这些点的编号p满足是某个整数v的倍数。但是这条铁路上还有一辆火车,它的车尾在点…

C语言——2.安装并使用VS

文章目录 1.编译器是什么2.编译器的选择2.1.VS2019/2022 的初步了解2.2.为什么不选择其他编译器呢? 3.编译器的安装过程(保姆级别教学)3.1.检查电脑版本3.2.下载安装包3.3.选择安装选项3.4.重启电脑3.5.创建账户登录3.6.颜色配置3.7.VS&#…

用GPT 搭建一个占星术、解梦、塔罗牌占卜和命理学服务

今天来尝试我们的占星术、解梦、塔罗牌占卜和命理学服务,揭开宇宙的奥秘并获得自我认识 聊天 GPT API 集成的 HTML5 模板。我们的目标是提供易于使用且高度可定制的 API 代码,使您能够训练自己的人工智能解决方案并将其添加到提示中。 我们的产品是可定…

Backtrader绘图cerebro.plot报错问题的处理

Backtrader绘图cerebro.plot报错问题的处理 1.问题描述 在jupyter 中使用BackTrader ,使用绘图功能时: cerebro.plot() 提示错误:ValueError: Axis limits cannot be NaN or Inf 由于backtrader 要求有7列数据,最后一列openint…

springcloud新闻发布系统源码

开发技术: jdk1.8,mysql5.7,nodejs,idea,vscode springcloud springboot mybatis vue elementui 功能介绍: 用户端: 登录注册 首页显示搜索新闻,新闻分类,新闻列表…

ES的常用查询

1 数据准备 1.1 创建索引和新增数据 先新增一条数据,新增数据时会自动创建索引 test_standard_analyzer。 PUT /test_standard_analyzer/_doc/1 {"remark": "This is a test doc" } 然后查询一下。 GET test_standard_analyzer/_search {&…

Redis 访问控制列表(ACL)

Redis ACL 关于 Redis ACL与旧版本兼容ACL helpACL 配置模式redis.conf 配置模式外部 ACL File 配置模式 ACL 规则总结 关于 Redis ACL Redis ACL (访问控制列表) 是 Access Control List 的缩写,它允许某些连接在可以执行的命令和可以访问的密钥方面受到限制。它…

5-什么是猴子补丁,有什么用途?什么是反射,python中如何使用反射?http和https的区别?

1 什么是猴子补丁,有什么用途 **解释**1 Python猴子补丁(Monkey Patch)是一种在运行时动态修改代码的技术。通在不修改源代码的情况下,改变代码的执行方式或增加功能2 Monkey Patching是在 运行时(run time) 动态替换属性(attrib…