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.mzph.cn/news/657301.shtml

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

相关文章

基于yolov2深度学习网络的视频手部检测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 输入mp4格式的视频文件进行测试,视频格式为1080p30. 2.算法运行软件版本 matlab2022a 3.部分核心程序 ..........................…

Linux第40步_移植ST公司的uboot

一、查看ST公司的uboot源码包 ST公司的uboot源码包在虚拟机中的路径: “/home/zgq/linux/atk-mp1/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/u-boot-stm32mp-2020.01-r0”; “u-boot-stm32mp-2020.01-r0”就是S…

Github 上传项目(个人令牌token)

1.点击 github头像 : setting -> Developer Settings -> Personal access tokens 2.在要上传的文件夹下运行以下命令: git init git commit -m "first commit" git branch -M main 利用以下命令模…

Vue中嵌入原生HTML页面

Vue中嵌入html页面并相互通信 需求&#xff1a;b2b支付需要从后获取到数据放到form表单提交跳转&#xff0c;如下&#xff1a; 但是vue目前暂时没找到有类似功能相关文档&#xff0c;所以我采用iframe嵌套的方式 1. Vue中嵌入Html <iframe src"/static/gateway.htm…

多线程c++

目录 1.join和detach区别 2.lock_guard和unique_lock 3.原子操作 4.条件变量condition_variable 5.future 和 promise 1.join和detach区别 ①不使用join和detach #include <iostream> #include <thread> #include <windows.h>using namespace std;v…

hcip---ospf综合实验

一&#xff1a;实验要求 1、R4为ISP&#xff0c;其上只能配置IP地址&#xff0c;R4与其所有直连设备间均使用公有IP 2、R3-R5/6/7为MGRE环境&#xff0c;R3为中心站点 3、整个OSPF环境IP基于R4的环回 4、所有设备均可访问R4的环回 5、减少LSA的更新量&#xff0c;加快收敛…

医院如何筛选安全合规的内外网文件交换系统?

医院内外网文件交换系统是专为医疗机构设计的&#xff0c;用于在内部网络&#xff08;内网&#xff09;和外部网络&#xff08;外网&#xff09;之间安全、高效地传输敏感医疗数据和文件的解决方案。这种系统对于保护患者隐私、遵守医疗数据保护法规以及确保医疗服务的连续性和…

初探分布式链路追踪

本篇文章&#xff0c;主要介绍应用如何正确使用日志系统&#xff0c;帮助用户从依赖、输出、清理、问题排查、报警等各方面全面掌握。 可观测性 可观察性不单是一套理论框架&#xff0c;而且并不强制具体的技术规格。其核心在于鼓励团队内化可观察性的理念&#xff0c;并确保由…

Django4.2(DRF)+Vue3 读写分离项目部署上线

文章目录 1 前端2 后端2.1 修改 settings.py 文件关于静态文件2.2 关于用户上传的文件图片 3 Nginx4 镜像制作4.1 nginx4.3 Django镜像4.3.1 构建 5 docker-compose 文件内容 1 前端 进入前端项目的根目录&#xff0c;运行如下命令进行构建 npm run build构建完成后&#xff…

K8S之Pod的介绍和使用

Pod的理论和实操 pod理论说明Pod介绍Pod运行与管理Pod管理多个容器Pod网络Pod存储 Pod工作方式自主式Pod控制器管理的Pod&#xff08;常用&#xff09; 创建pod的流程 pod实操通过资源清单文件创建自主式pod通过kubectl run创建Pod&#xff08;不常用&#xff09; pod理论说明 …

计算机网络基础知识笔记

1 HOST和PORT Claude-2-100k: 我来用简单的语言解释一下HOST和PORT这两个网络术语&#xff1a; HOST就是设备的IP地址,比如"127.0.0.1"就是我们本机的IP地址&#xff1b; PORT就是端口号,可以把它简单理解为设备上的门牌号。 举个类比,我们要给某个人发信件,需要知道…

指针的深入了解6

1.回调函数 回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一个函数&#xff0c;当这个指针被用来调用其所指向的函数 时&#xff0c;被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用&#xff0…

华为HCIE课堂笔记第十七章 广域网互联技术

第十七章 广域网互联技术 17.1 GRE VPN GRE VPN用于分支与分支通过私网地址互联&#xff0c;通过在私网报文上添加一个GRE的头部&#xff0c;以及添加一层外层的IP头部&#xff0c;通过外层头部中的目IP地址使得报文到达隧道对端接口&#xff0c;并解封装得到原始的私网报文…

高通GAIA V3命令参考手册的研读学习(十二):第四个示例

主机和设备交互通信的第四个示例&#xff0c;也就最后一个示例&#xff1a;设置默认音量失败 这个示例描述了主机发送一个设置默认音量的请求&#xff0c;其中包含一个无效的值。 对于设备而言&#xff0c;因为语音音量的有效范围是0到15 (0x00到0x0f)&#xff0c;也就是一共…

Python的类(Class)和描述器(Descriptor)

1. 背景 笔者的大数据平台XSailboat的SailWorks模块包含离线分析功能。离线分析的后台实现&#xff0c;包含调度引擎、执行引擎、计算引擎和存储引擎。计算和存储引擎由Hive提供&#xff0c;调度引擎和执行引擎由我们自己实现。调度引擎根据DAG图和调度计划&#xff0c;安排执…

【DOCKER】docker 安装sonarque

安装docker 安装docker https://blog.csdn.net/BThinker/article/details/123358697 加入阿里云镜像 https://blog.csdn.net/TommyXu8023/article/details/113291112 { "registry-mirrors": ["https://alzgoonw.mirror.aliyuncs.com"] }安装sonarqube ht…

【LVGL源码移植环境搭建】

LVGL源码移植&环境搭建 ■ LVGL源码移植■ 下载LVGL源码■ 修改LVGL文件夹■■■■ 视频链接 Ubuntu模拟器环境建置 ■ LVGL源码移植 ■ 下载LVGL源码 LVGL源码 我们以选择v8.2.0为例&#xff0c;选择8.2.0下载 ■ 修改LVGL文件夹 1.我们只需要关注这5个文件即可&…

《Docker技术革命:从虚拟机到容器化,全面解析Docker的原理与应用-上篇》

文章目录 Docker为什么会出现总结 Docker的思想Docker历史总结 Docker能干嘛虚拟机技术虚拟机技术的缺点 容器化技术Docker和虚拟机技术的区别 Docker概念Docker的基本组成镜像&#xff08;image)容器&#xff08;container&#xff09;仓科&#xff08;repository&#xff09;…

GitHub工作流的使用笔记

文章目录 前言1. 怎么用2. 怎么写前端案例1&#xff1a;自动打包到新分支前端案例2&#xff1a;自动打包推送到gitee的build分支案例3&#xff1a;暂时略 前言 有些东西真的就是要不断的试错不断地试错才能摸索到一点点&#xff0c;就是摸索到凌晨两三点第二天要8点起床感觉要…

JDK8对List对象根据属性排序

文章目录 JDK8对List对象根据属性排序1. 被排序字段为null或者空时候报错2. 使用Stream流排序2.1 根据name升序2.2 根据name升序&#xff0c;score降序 3. 使用Collections排序3.1 根据name升序3.2 根据name升序&#xff0c;score降序 4. 完整的demo JDK8对List对象根据属性排序…