Java多线程--同步机制解决线程安全问题方式二:同步方法

文章目录

  • 一、同步方法
    • (1)同步方法--案例1
      • 1、案例1
      • 2、案例1之同步监视器
    • (2)同步方法--案例2
      • 1、案例2之同步监视器的问题
      • 2、案例2的补充说明
  • 二、代码及重要说明
    • (1)代码
    • (2)重要说明

一、同步方法

同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着

🗳️格式:

public synchronized void method(){可能会产生线程安全问题的代码
}

(1)同步方法–案例1

1、案例1

还是拿这个例子来说,方式一实现Runnable接口,如下:

🌱代码

package yuyi02;/*** ClassName: WindowTset2* Package: yuyi02* Description:*		使用同步方法解决实现Runnable接口的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 9:52*/
public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)int ticket=100;@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (true){if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{break;}}}
}

🍺输出结果(部分)

image.png

可以看到,出现了重票和错票


现在来解决这个安全问题。

操作ticket的代码:

image.png

现在将他们完全声明在一个方法show()当中,然后在while里面调用show()方法。比如:

image.png

我们可以将while里面的show()synchronized包裹,就是同步代码块的方式,如下:

public void run() { //2.实现接口中的抽象方法run()方法while (true){synchronized (this) {show();}}
}

当然也可以将show方法声明为同步方法

现在这里有点错误,就是break的问题。之前是在while里面写的,现在将if-else从while里面抽出来了,所以break就不行了。
将break直接删掉吗?不行,这样的话程序就不能自己结束了。如下:

image.png

我们可以声明一个变量isFlag,初始化为true。如下:

image.png

🌱代码

public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)int ticket=100;boolean isFlag=true;public void show(){if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{isFlag=false;}}@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (isFlag){synchronized (this) {show();}}}
}

🍺输出结果(部分)

image.png

但是现在还是用的“同步代码块”来解决问题。


现在操作ticket的代码完全写在了show()方法里面,那么将这个show方法加一个同步即可,就是直接加一个synchronized,如下:

image.png

🌱代码

public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)int ticket=100;boolean isFlag=true;public synchronized void show(){ //此时的同步监视器就是:this 。此题目中是s,是唯一的,线程安全if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{isFlag=false;}}@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (isFlag){show();}}
}

🍺输出结果(部分)

image.png

可以。


2、案例1之同步监视器

上面的show()方法中,我们没有显示得去写同步监视器,其实它是默认的。

针对于这个同步方法,若这个方法是非静态的,那么这个同步监视器默认的就是this

image.png

这个this是改不了的,我们只能考虑这个this是不是唯一的。

🎲此时this是唯一的吗?

是唯一的。因为现在实在当前实现方式里面写的,类SaleTicket2的对象只造了一个,并且被多个线程所共用。所以调用方法的时候,只有唯一的对象s

如下:

image.png

所以线程是安全的,没有问题。

(2)同步方法–案例2

1、案例2之同步监视器的问题

还是拿这个例子来说,方式二继承Thread类,如下:

🌱代码

package yuyi02;/*** ClassName: WindowTest3* Package: yuyi02* Description:*      使用同步方法解决继承Thread类的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 11:03*/
public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类//票static int ticket=100;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (true){if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{break;}}}
}

🍺输出结果(部分)

image.png

出现了重票的问题。


现在来解决这个安全问题。

操作ticket的代码:

image.png

将上述操作ticket的代码放在方法show1()中,如下:

image.png

跟上一个案例类似,将break去掉,加一个isFlag1,如下:

image.png

当然,isFlag都要共用一个,所以需要加上static,如下:

image.png


🌱代码

public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类//票static int ticket=100;static boolean isFlag1=true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1){show1();}}public void show1(){if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{isFlag1=false;}}
}

🍺输出结果(部分)

现在还没有解决线程安全问题,所以输出结果还是有重票的,如下:

image.png


现在我们直接给show1()方法加上synchronized,可以吗?

如下:

image.png

🌱代码

public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类//票static int ticket=100;static boolean isFlag1=true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1){show1();}}public synchronized void show1(){   //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{isFlag1=false;}}
}

🍺输出结果(部分)

image.png

此时是非静态同步方法,同步监视器就是this,此问题中的this有:w1,w2,w3(当前类的对象造了三个)。所以肯定不行


2、案例2的补充说明

上面那个既然不行,那该怎么办呢?

show()方法改成静态的吗?如下:

image.png

🌱代码

public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread {    //卖票  1.创建一个继承于Thread类的子类//票static int ticket = 100;static boolean isFlag1 = true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1) {show1();}}public static synchronized void show1() {   //静态方法的同步监视器是:当前类,就是Window.classif (ticket > 0) {   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;} else {isFlag1 = false;}}}

🍺输出结果(部分)

image.png

现在这个案例加上static是可行的。

静态方法的同步监视器是当前类本身,就是Window.class(这里是一个对象,一个值,不是类),是唯一的。所以现在是安全的。

🍰说明

这个方法能不能改成静态的,需要看具体的问题。适合就可以改,不适合就不要改了。

若有的方法就是一个实例方法,里面要用实例变量,那就不适合改,同步方法就不靠谱了。

所以这里不要刻意去满足同步方法让它去达到我们的要求(不要为了线程安全,去特意将方法改为静态的)。

有的时候这个方法就不适合加上静态,同步方法就不适合去做了,就不要使用同步方法了。

那我们就主动将操作ticket的代码用synchronized包裹一下,然后指定一个同步监视器即可。

二、代码及重要说明

(1)代码

①【使用同步方法解决实现Runnable接口的线程安全问题】

🌱代码

package yuyi02;/*** ClassName: WindowTset2* Package: yuyi02* Description:*      使用同步方法解决实现Runnable接口的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 9:52*/
public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)int ticket=100;boolean isFlag=true;public synchronized void show(){    //此时的同步监视器就是:this 。此题目中是s,是唯一的,线程安全if(ticket>0){   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;}else{isFlag=false;}}@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (isFlag){show();}}
}

②【使用同步方法解决继承Thread类的线程安全问题】

🌱代码

package yuyi02;/*** ClassName: WindowTest3* Package: yuyi02* Description:*      使用同步方法解决继承Thread类的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 11:03*/
public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口  创建当前Thread的子类的对象Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread {    //卖票  1.创建一个继承于Thread类的子类//票static int ticket = 100;static boolean isFlag1 = true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1) {show1();}}public static synchronized void show1() {   //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3,线程仍然不安全,加一个staticif (ticket > 0) {   //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100ticket--;} else {isFlag1 = false;}}}

(2)重要说明

【同步方法】

🗳️格式:

public synchronized void method(){可能会产生线程安全问题的代码
}

🚗说明

  • 如果操作共享数据的代码(需要被同步的代码)完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
  • 非静态的同步方法,默认同步监视器是this静态的同步方法,默认同步监视器是当前类本身

☕注意

现在咱们线程一共说了这么几件事情,如下:

image.png

下面来看一下这个关键字:synchronized(同步的)

  • 好处:解决了线程的安全问题
  • 弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低

卖票:三个线程来做,交互去执行。

当一个线程还没有操作完,其他线程也过来了,就会出现安全问题。

执行前面代码的时候,三个线程没有共享数据,同时执行也没有问题。

但是在执行共享数据的代码的时候,只能让一个线程进去,其他线程在外面等着。

也就是说,执行前面代码的时候,三个线程可以并发执行,但是在操作共享数据的时候,一定是串行的去执行,也就是只有一个线程可以进去执行。所以性能会差一点

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

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

相关文章

用GPT写PHP框架

参考https://www.askchat.ai?r237422 写一个mvc框架 上面是简单的案例,完整的PHP框架,其核心通常包含以下几个关键组件: 1. 路由(Routing):路由组件负责解析请求的URL,并将其映射到相应的控制…

Springboot项目启动后浏览器不能直接访问接口,而postman可以访问?

在云服务器上部署springboot后端时,项目启动后浏览器不能直接访问接口,而postman可以访问。这是当时困扰了我大半天的小问题,在我打开防火墙和阿里云安全组之后还是没解决。然后在网上搜了很多很多资料,以为是浏览器访问权限或者是https什么证…

【乳腺肿瘤诊断分类及预测】基于LVQNN学习向量量化神经网络

课题名称:基于LVQ神经网络的乳腺肿瘤诊断(类型分类) 版本日期:2023-03-10 运行方式: 直接运行0501_LVQ0501.m 文件即可 代码获取方式:私信博主或QQ:491052175 模型描述: 威斯康辛大学医学院…

H.264与H.265的主要差异

H.265仍然采用混合编解码,编解码结构域H.264基本一致, H.265与H.264的主要不同 编码块划分结构:采用CU (CodingUnit)、PU(PredictionUnit)和TU(TransformUnit)的递归结构。 并行工具:增加了Tile以及WPP等并行工具集以提高编码速…

优化器刺客之limit 1--Order by col limit n 代价预估优化探索

一、现象 order by 排序加了limit后更慢了? test# explain analyze select userid from dba_users where username like %aaaaaaaaaaaaaaaaaa% order by userid ;QUERY PLAN --------------…

第4章 python深度学习——(波斯美女)

第4章 机器学习基础 本章包括以下内容: 除分类和回归之外的机器学习形式 评估机器学习模型的规范流程 为深度学习准备数据 特征工程 解决过拟合 处理机器学习问题的通用工作流程 学完第 3 章的三个实例,你应该已经知道如何用神经网络解决分类问题和回归…

调试小结:PHY初始化前后Link Status是否能正确反应网线插上、拔下状态

1 说明 为了验证是否需要初始化PHY才能检测到网线插上、拔下,这里我们对比初始化PHY(LAN8720)前后,插拔网线PHY寄存器1的bit2的是否按照预期变化来进行测试。 我们查看的PHY寄存器是1的bit2,定义如下: 2…

anaconda离线安装包的方法

当设备没有网络时,可以使用有网络的设备先下载所需安装包,然后离线拷贝到需要安装的设备,最后安装。 一. 下载所需安装包 下载命令:使用pip download。详细描述参见pip download -h 以"blind-watermark"为例。 pip …

asp.net core监听本地ip地址

开发asp.net core的时候遇到一个问题我想提供访问供其他同事测试,但是默认都是localhost或者127.0.0.1。我想换成我的Ip地址访问但是不行,百度搜索需要更换监听的地址即修改launchSettings.json,修改为0.0.0.0:5248,这样不管local…

回归预测 | Matlab实现CPO-SVR冠豪猪优化支持向量机的数据多输入单输出回归预测

回归预测 | Matlab实现CPO-SVR冠豪猪优化支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab实现CPO-SVR冠豪猪优化支持向量机的数据多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现CPO-SVR冠豪猪优化支持向量机的数据多输入…

visual studio2022专业版安装步骤

目录 一、Visual studio下载二、创建C#项目——Hello World三、专业版秘钥激活 一、Visual studio下载 首先进入下载官网 先下载2022专业版,等等后面还需要选环境 我勾选了以下几个和c#开发有关的,后面缺什么还可以再安装所有以少勾了问题也不大 然后…

故障脚本的重要作用:预防、诊断与恢复

故障脚本是在信息技术和计算机领域中广泛使用的一种自动化工具,它们的主要目的是预测、诊断和修复系统或软件中的故障。这些脚本在现代技术环境中扮演着至关重要的角色,本文将介绍故障脚本的主要作用。 一、预防性作用 监控和预警 故障脚本可以用于监控系…

C# 一个快速读取写入操作execl的方法封装

这里封装了3个实用类ExcelDataReaderExtensions,ExcelDataSetConfiguration,ExcelDataTableConfiguration和一个实用代码参考: using ExcelDataReader; using System; using System.Collections.Generic; using System.Linq; using System.T…

GitHub国内打不开(解决办法有效)

最近国内访问github.com经常打不开,无法访问。 github网站打不开的解决方法 1.打开网站http://tool.chinaz.com/dns/ ,在A类型的查询中输入 github.com,找出最快的IP地址。 2.修改hosts文件。 在hosts文件中添加: # localhost n…

leetcode 27.移除元素(python版)

需求 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度…

【新课】安装部署系列Ⅲ—Oracle 19c Data Guard部署之两节点RAC部署实战

本课程由云贝教育-刘峰老师出品,感谢关注 课程介绍 Oracle Real Application Clusters (RAC) 是一种跨多个节点分布数据库的企业级解决方案。它使组织能够通过实现容错和负载平衡来提高可用性和可扩展性,同时提高性能。本课程基于当前主流版本Oracle 1…

图的学习

图的基本概念和术语 图的定义:图是由顶点的有穷非空集合和顶点之间的边的集合组成的,G表示,V是图G中顶点的集合,E是图G中边的集合 无向图:任意两点的边都是无向边组成的图(无向边:&#xff08…

【服务端性能测试】性能测试策略如何做

一、需求收集 先需要确认本次测试目的是什么,然后再看我们需要用什么参数来判断这个目的是否能够达成。 1.1 业务性能指标参考: TPS、QPS、RT、请求成功率(一般请求成功率>99.99%) 1.2 硬件性能指标参考: 即服…

TensorFlow2实战-系列教程11:RNN文本分类3

🧡💛💚TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 6、构建训练数据 所有的输入样本必须都是相同shape(文本长度,…

Spring | Spring的“数据库开发“ (Srping JDBC)

目录: Spring JDBC1.Spring JDBC的核心类 ( JdbcTemplate类 )2.Srping JDBC 的配置3.JdbcTemplate类的“常用方法”execute( ):直接执行“sql语句”,没有返回值update( ) :“增删改”,返回 “影响的行数”query( ) : “…