多线程:线程安全、线程同步、线程通信

 线程安全

什么是线程安全问题?

  • 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

1.线程安全问题出现的原因?

  • 存在多个线程在同时执行
  • 同时访问一个共享资源
  • 存在修改该共享资源

用程序模拟线程安全问题

取钱案例
需求:
小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,模拟2人同时去取钱10万
分析:

  1. 需要提供一个账户类,接着创建一个账户对象代表2个人的共享账户
  2. 需要定义一个线程类(用于创建两个线程,分别代表小明和小红)
  3. 创建2个线程,传入同一个账户对象给2个线程处理
  4. 启动2个线程,同时去同一个账户对象中取钱10万
//测试类public class ThreadTest {public static void main(String[] args) throws Exception {//1.创建一个账户对象,代表两个人共享账户Account acc = new Account("ICBC-110", 100000);//2.创建两个线程,(创建一个线程类)分别代表小红,小明,再去同一个账户中取钱、// -》就需要把同一个账户对象交给两个线程//通过DrawThread有参构造器把同一个账户对象交给两个线程Thread t1 = new DrawThread(acc, "小明");//小明t1.start();//t1.join();//让小明这个线程先执行
// ——名字正好是线程的名字——》有参构造器多接一个名字的参数用name来接再通过super送给Thread提供的有参构造器new DrawThread(acc, "小红").start();//小红}
}
//账户类
import javax.print.DocFlavor;
import java.security.PrivateKey;public class Account {private String IdCard;private double money;//账户的余额public Account() {}public Account(String idCard, double money) {this.IdCard = idCard;this.money = money;}//小明小红会同时取钱public void drawMoney(double money) {//取钱的金额也叫money——取钱的金额//首先得知道是谁来取钱——》可以为线程设置一个名字在ThreadTest类里边String name = Thread.currentThread().getName();//得到当前线程对象//1.判断余额是否足够  //this是一个变量,表示当前方法调用者的地址值if (this.money >= money) {//this.money指的是当前账户的余额System.out.println(name + "来取钱,取" + money + "钱成功!");//取完钱之后更新一下余额this.money = this.money - money;System.out.println(name + "取钱后余额为" + this.money);} else {System.out.println(name + "来取钱,余额不足");}}public String getIdCard() {return IdCard;}public void setIdCard(String idCard) {IdCard = idCard;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}}
//线程类
public class DrawThread extends Thread {private Account acc;//创造有参构造器public DrawThread(Account acc, String name) {//接账户类型的对象-》怎么样能让账户对象能够被run方法使用来取钱?-》再往上面定义一个成员变量——》类型就是账户类型,名字就叫acc//我们就会把接到的账户对象交给定义的成员变量acc,此时就能用acc取钱//怎么交给他?super(name);//再通过super送给Thread提供的有参构造器this.acc = acc;//小明线程对象接了acc这个账户对象把他交给上面的成员变量acc}@Overridepublic void run() {//取钱 -》用这个账户对象取钱10wacc.drawMoney(100000);}
}

线程同步

认识线程同步

线程同步就是解决线程安全问题的方案。
线程同步的思想就是让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

线程同步的常见方案

  • 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来

方式一:同步代码块

  • 作用:把访问共享资源的核心代码给上锁,以此保证线程安全

  • 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

同步锁的注意事项

  • 对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

锁对象用的是一个字符串的"锁",而字符串的"锁"在我们的系统中永远只有一份,也就意味着这个对象对于再多出来的其他线程来说也都是同一个对象,而现在用"锁"这个对象来作为锁,就会带来很大的问题,(第一个共享资源的1号抢到这个对象“锁”作为锁了,此时不光会锁住第一个共享资源的2号3号,也会锁住其他共享资源的其他人,导致其他人也不能“取钱”;实际上1号抢到锁只需要锁住2号3号就行了,不能锁住“其他家庭的人”。现在之所以会出现这个问题就是因为现在这个“锁”对象锁的范围太大了,对于所有的线程来说都是同一个对象,就不合适了。应该是自己家的人有一个锁对象,“一家一个锁对象”,这样才会控制每一家人的线程访问情况。)怎么做???

官方建议:在这个地方要用共享资源作为锁。(这个地方是一个实例方法,就应该用this作为锁,此时this正好代表共享资源。代码例子中,小明小红这两个线程的话,此时this正好代表的是acc账户;同理,如果是小黑小白两个线程进行到drawMoney这来的话,小黑小白拿的是acc1这个账户,此时this代表的是acc1这个账户,他就只会锁住小黑和小白,这样就不会干扰别人了。)

注意:在实例方法中,建议使用this作为锁,正好代表线程的共享资源的。就不会出现一些问题。

拓展:可能会遇到多个线程调用静态方法,(假设静态方法是有50个线程在调用他)应该用什么锁?

答:如果是静态方法,官方建议我们使用类名.class作为锁。类名.class他是一个字节码文件,这个class文件在系统中只有一份,那为什么静态方法就建议class作为锁呢?这是因为静态方法就是被所有线程通过这个类名来进行访问的,现在要锁住所有的线程,就直接用类名.class(本身就只有一份)锁住当前访问这个静态方法的所有线程,只允许一个线程进来就可以了。合理!

总结:

锁对象随便选择一个唯一的对象好不好呢?不好,会影响其他无关线程的执行

锁对象的使用规范:

  • 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象
//同步代码块
/***ctrl+alt+t 将访问共享资源(访问账户在同时取钱)的核心代码,放到同步代码块里面,对他进行上锁*同步代码块还需要声明一个所谓的同步锁,就是用一个Java对象表示的一把锁*而且锁要求对应当前执行的线程必须是同一个对象才可以*双引号给出的一个对象在计算机中(常量池)只有一份,对于当前执行的线程来说肯定是同一个对象——》当然 *可以锁住线程,只允许一个线程进来*/
synchronized ("锁") {if (this.money >= money) {System.out.println(name + "来取钱,取" + money + "钱成功!");this.money = this.money - money;System.out.println(name + "取钱后余额为" + this.money);} else {System.out.println(name + "来取钱,余额不足");}}


方式二:同步方法

  1. 作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

  • 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法底层原理

  1. 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  2. 如果方法是实例方法:同步方法默认用this作为的锁对象。
  3. 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

是同步代码块好还是同步方法好一点?

  • 范围上:同步代码块锁的范围更小,同步方法锁的范围更大,
  • 可读性:同步方法更好

底层是有一个隐含的锁的,如果方法是实例方法隐含的锁是用this作为锁的,只是看不到,(this此时正好代表他们共享的acc账户),每次只允许一个人进来访问这个锁,访问完毕后自动解锁,其他线程进来就不会有安全问题。如果以后方法是静态方法的话,也可以用synchronized把他声明成一个同步方法,这样也能够保证线程安全,只是默认隐含的锁是类名.class作为锁。

//同步方法public synchronized void drawMoney(double money) {String name = Thread.currentThread().getName();if (this.money >= money) {System.out.println(name + "来取钱,取" + money + "钱成功!");this.money = this.money - money;System.out.println(name + "取钱后余额为" + this.money);} else {System.out.println(name + "来取钱,余额不足");}}

方式三:Lock锁

  • LocK锁是IDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
  • Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

Lock的常用方法

                                               

private final Lock lk = new ReentrantLock();
//创建一个Lock锁对象
//final->lk记录的这个锁对象是不能被替换的,将锁对象进行一个保护
//是下面的取钱方法要用锁对象,所以锁对象要放在账户类里面,方便下面使用
//实例变量,属于一个对象变量。——》每一个账户对象都应该有一个锁对象
//放在这里创建锁对象定成一个实例变量的形式:是因为创建账户对象的时候,同时也可以创建一个属于这个账户的唯一一个锁对象出来
//Lock锁public  void drawMoney(double money) {String name = Thread.currentThread().getName();lk.lock();//加锁:意思是如果有多个线程对象执行到这来,只允许一个线程进行加锁再进来,执行完毕后应该解锁try {//即便加锁之后中间程序出现问题、bug被拦截之后最终还是能进行解锁,这样就比较安全//好处就在于一个线程进来出现问题,最终还是会解锁,其他线程还是能进来跑if (this.money >= money) {System.out.println(name + "来取钱,取" + money + "钱成功!");this.money = this.money - money;System.out.println(name + "取钱后余额为" + this.money);} else {System.out.println(name + "来取钱,余额不足");}} catch (Exception e) {e.printStackTrace();} finally {lk.unlock();//解锁}}

线程通信【了解】

什么是线程通信?

  • 当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

线程通信的常见模型(生产者与消费者模型

  • 生产者线程负责生产数据
  • 消费者线程负责消费生产者生产的数据。
  • 注意:生产者生产完数据应该等待自己,通知消费者消费消费者消费完数据也应该等待自己,再通知生产者生产。

Object类的等待和唤醒方法:(写代码时顺序应该先唤醒再等待!!!!!!!!!!!

注意:上述方法应该使用当前同步锁对象进行调用。

无论这里发生了什么情况,谁先抢到锁谁后抢到锁,最终一定是一个生产->消费的过程(做一个包子->吃一个包子)

//需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
// 2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。
public class ThreadTest {public static void main(String[] args) {Desk desk = new Desk();//需要创建三个生产者线程(三个厨师)//用匿名内部类的方式创建线程/* new Thread(new Runnable() {@Overridepublic void run() {}}).start();*///Lambdanew Thread(() -> {while (true) {//不断的抢桌子放包子desk.put();}},"厨师1").start();new Thread(() -> {while (true) {//不断的抢桌子放包子desk.put();}},"厨师2").start();new Thread(() -> {while (true) {//不断的抢桌子放包子desk.put();}},"厨师3").start();//创建2个消费者(2个吃包子的)new Thread(() -> {while (true) {//不断的抢桌子放包子desk.get();}},"吃货1").start();new Thread(() -> {while (true) {//不断的抢桌子放包子desk.get();}},"吃货2").start();}
}

import java.util.ArrayList;
import java.util.List;//桌子上面要放一个包子
public class Desk {private List<String> list = new ArrayList<>();//放包子(一个)//厨师1 厨师2 厨师3public synchronized void put() {try {String name = Thread.currentThread().getName();//拿到名字就知道是哪一个厨师进来了//判断是否有包子if (list.size()==0){list.add(name + "做了一个肉包子");System.out.println(name+"做了一个肉包子");Thread.sleep(2000);//做包子花了2s//把异常trycatch不要往外面抛不然去外面还得抛//线程通信的重点:等待自己,不去竞争cpu资源和锁,把自己暂停 (提高整体的性能)——》对应的是唤醒别人this.notifyAll();this.wait();}else {//厨师再进来就会发现有包子,不做——》也就该等待自己,唤醒别人this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}//拿包子//吃货1 吃货2public  synchronized void get() {try {String name = Thread.currentThread().getName();//拿到名字就知道是哪一个吃货进来了if (list.size()==1){//有包子,吃掉System.out.println(name+"吃掉"+list.get(0));//从集合里边把包子取出来,因为是放一个包子就是索引0list.clear();//吃完之后把集合清空——》唤醒别人,等待自己Thread.sleep(1000);//吃包子花了1sthis.notifyAll();this.wait();}else {//没有包子,唤醒别人,等待自己this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}
}

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

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

相关文章

2025张宇考研数学基础36讲,视频百度网盘+PDF

一、张宇老师全年高数体系&#xff08;听课用书指南&#xff09; 25张宇全程&#xff1a; docs.qq.com/doc/DTmtOa0Fzc0V3WElI 复制粘贴在浏览器上打开&#xff0c;就可以看到2025张宇的全部的啦&#xff01; 一般来说我们把考研数学划分为3-4个阶段&#xff0c;分别是基础阶…

2. IS-IS 基础实验

2.1 IS-IS 配置实验 2.1.1 实验介绍 2.1.1.1 学习目标 1. 实现 IS-IS 协议基本配置 2. 实现 IS-IS 协议 DIS 优先级修改 3. 实现 IS-IS 协议网络类型修改 4. 实现 IS-IS 协议外部路由引入 5. 实现 IS-IS 接口 cost 修改 6. 实现 IS-IS 路由渗透配置 2.1.1.2 实验组网介…

Git——标签详解

目录 Git标签1、概述1.1、标签是什么1.2、什么时候使用标签1.3、标签的分类 2、轻量标签&#xff08;lightweight tag&#xff09;3、有附注的标签&#xff08;annotated tag&#xff09;4、两种标签的区别5、删除标签 Git标签 1、概述 1.1、标签是什么 在Git中&#xff0c;…

Java基于springboot的篮球NBA球队管理系统ssm

本次将以NBA球队管理方面为切入点&#xff0c;论述了NBA球队管理的意义和内容&#xff0c;以此展开对NBA球队管理系统的开发与建设的详细分析。从数据挖掘的角度出发&#xff0c;了解信息管理系统的作用&#xff0c;对NBA球队管理系统的过程以及用处进行更深一步的研究&#xf…

Java 世界破破烂烂,电音小猫缝缝补补

Java 世界破破烂烂&#xff0c;电音小猫缝缝补补 Java 通用代码生成器光 2.4.0 电音之王尝鲜版六正在研发&#xff0c;昨天发布了介绍视频&#xff0c;请见&#xff1a; https://www.bilibili.com/video/BV1yD421j7UP/ 电音之王尝鲜版六支持哑数据模式&#xff0c;支持枚举。…

win10笔记本在显示设置中不慎将主显示器禁用掉导致开机黑屏的解决方案

因为笔记本电脑的显示扩展接口有问题&#xff0c;所以在电脑开机之后&#xff0c;会误识别出几个不存在的扩展屏幕&#xff0c;所以我就想从显示设置中将这几个误识别出来的扩展屏幕禁用掉&#xff08;不然鼠标总是移动到主屏幕边界之外的地方&#xff09;&#xff0c;在显示设…

RabbitMQ介绍及搭建

架构 RabbitMQ是实现了高级消息队列协议&#xff08;AMQP&#xff09;的开源消息代理软件&#xff0c;使用erlang语言编写&#xff0c;依赖Erlang环境运行。 Broker&#xff1a;运行消息队列服务进程的节点&#xff0c;包含Exchange、Queue&#xff1b; Producer&#xff1a;消…

蓝桥杯刷题总结(Python组)

1、蛇形矩阵 解题思路&#xff1a;每次赋值后都对方向进行改变&#xff0c;一般上下左右就是&#xff08;-1&#xff0c;0&#xff09;&#xff0c;&#xff08;0&#xff0c;1&#xff09;&#xff0c;&#xff08;1&#xff0c;0&#xff09;&#xff0c;&#xff08;0&…

TSINGSEE青犀AI智能分析网关V4酿酒厂安全挂网AI检测算法

在酿酒行业中&#xff0c;安全生产一直是企业经营中至关重要的一环。为了确保酒厂生产过程中的安全&#xff0c;TSINGSEE青犀AI智能分析网关V4的安全挂网AI检测算法发挥了重要作用。 TSINGSEE青犀AI智能分析网关V4的安全挂网检测算法是针对酒厂里酒窖挂网行为进行智能检测与识…

爬虫 Day2

resp.close()#关掉resp 一requests入门 &#xff08;一&#xff09; 用到的网页&#xff1a;豆瓣电影分类排行榜 - 喜剧片 import requestsurl "https://movie.douban.com/j/chart/top_list" #参数太长&#xff0c;重新封装参数 param {"type": "…

【Qt问题】使用QSlider创建滑块小部件无法显示

问题描述&#xff1a; 使用QSlider创建滑块小部件用于音量按钮的时候&#xff0c;无法显示&#xff0c;很奇怪&#xff0c;怎么都不显示 一直是这个效果&#xff0c;运行都没问题&#xff0c;但是就是不出现。 一直解决不了&#xff0c;最后我在无意中&#xff0c;在主程序中…

LLM 面试知识点——模型基础知识

1、主流架构 目前LLM(Large Language Model)主流结构包括三种范式,分别为Encoder-Decoder、Causal Decoder、Prefix Decode。对应的网络整体结构和Attention掩码如下图。 、 各自特点、优缺点如下: 1)Encoder-Decoder 结构特点:输入双向注意力,输出单向注意力。 代表…

MySQL中数据库表的监控

MySQL中数据库表的监控 &#xff08;1&#xff09;查看数据库中当前打开了哪些表&#xff1a;show OPEN TABLES &#xff0c;如图6-1-5所示。另外&#xff0c;还可以通过show OPEN TABLES where In_use > 0过滤出当前已经被锁定的表。 查看数据库中表的状态&#xff1a;SHO…

css3 实现html样式蛇形布局

文章目录 1. 实现效果2. 实现代码 1. 实现效果 2. 实现代码 <template><div class"body"><div class"title">CSS3实现蛇形布局</div><div class"list"><div class"item" v-for"(item, index) …

聚类分析 | Matlab实现基于NNMF+DBO+K-Medoids的数据聚类可视化

聚类分析 | Matlab实现基于NNMFDBOK-Medoids的数据聚类可视化 目录 聚类分析 | Matlab实现基于NNMFDBOK-Medoids的数据聚类可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 NNMFDBOK-Medoids聚类&#xff0c;蜣螂优化算法DBO优化K-Medoids 非负矩阵分解&#xff08…

【机器学习-01】机器学习基本概念与建模流程

机器学习的过程本质上是一个不断通过数据训练来提升模型在对应评估指标上表现的过程。在此过程中&#xff0c;为模型提供有效的反馈并基于这些反馈进行持续的调整是至关重要的。只有当这个过程顺利进行时&#xff0c;模型才能得到有效的训练&#xff0c;机器才能真正实现学习。…

鸿蒙4.0ArkUI快速入门(一)应用模型

ArkUI篇 应用模型Stage模型FA模型模型对比 应用模型 应用模型是HarmonyOS为开发者提供的应用程序所需能力的抽象提炼&#xff0c;它提供了应用程序必备的组件和运行机制。 HarmonyOS先后提供了两种应用模型&#xff1a; FA&#xff08;Feature Ability&#xff09;模型&…

Machine Learning ---- Gradient Descent

目录 一、The concept of gradient&#xff1a; ① In a univariate function&#xff1a; ②In multivariate functions&#xff1a; 二、Introduction of gradient descent cases&#xff1a; 三、Gradient descent formula and its simple understanding: 四、Formula o…

MATLAB教程

目录 前言一、MATLAB基本操作1.1 界面简介1.2 搜索路径1.3 交互式命令操作1.4 帮助系统 二、MATLAB语言基础2.1 数据类型2.2 MATLAB运算2.2.1 算数运算2.2.2 关系运算2.2.3 逻辑运算 2.3 常用内部函数2.4 结构数据与单元数据 三、MATLAB程序设计3.1 M文件3.2 函数文件3.3 程序控…

1、Jenkins持续集成-介绍

文章目录 1、软件开发生命周期1.1 软件开发瀑布模型 2、软件的敏捷开发2.1 什么是敏捷开发&#xff1f;2.2 敏捷开发是如何迭代&#xff1f;2.3 敏捷开发有什么好处&#xff1f; 3、什么是持续集成4、持续集成的组成要素5、持续集成的好处6、Jenkins介绍 PS&#xff1a;本篇都是…