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.hqwc.cn/news/442711.html

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

相关文章

刨析数据结构(一)

🌈个人主页:小田爱学编程 🔥 系列专栏:数据结构————"带你无脑刨析" 🏆🏆关注博主,随时获取更多关于数据结构的优质内容!🏆🏆 😀欢迎…

【计算机图形】几何(Geometry)和拓扑(Topology)

目录 参考文献三维实体建模内核CSG/BREPParasolid简介Parasolid接口函数Parasolid类的结构 Parasolid数据分类:几何(Geometry)和拓扑(Topology)拓扑(Topology)什么是“拓扑”呢?Principle Geometry- Topology - Construction Geometry案例:拓…

设计与实现基于Java+MySQL的考勤发布-签到系统

课题背景 随着现代经济的迅速发展,电子考勤签到服务已经渗透到人们生活的方方面面,成为不可或缺的一项服务。在这个背景下,线上签到作为考勤签到的一种创新形式,为用户提供了便捷的操作方式,使得任务签到、个人签到记…

bat脚本:批量生成创建数据库的SQL语句

需求来源:使用 Navicat等数据库工具点击“转储SQL文件”会生成一个 xxx.sql 的文件,xxx是导出的数据库名。导出的数据库多了,就会一次性生成很多这样的SQL文件,所以需要写个脚本根据这些SQL脚本文件来批量生成创建数据库的SQL语句…

《Pandas 简易速速上手小册》第1章:Pandas入门(2024 最新版)

文章目录 1.1 Pandas 简介1.1.1 基础知识1.1.2 案例:气候变化数据分析1.1.3 拓展案例一:金融市场分析1.1.4 拓展案例二:社交媒体情感分析 1.2 安装和配置 Pandas1.2.1 基础知识1.2.2 案例:个人财务管理1.2.3 拓展案例一&#xff1…

大创项目推荐 题目:基于深度学习卷积神经网络的花卉识别 - 深度学习 机器视觉

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

#RAG|NLP|Jieba|PDF2WORD# pdf转word-换行问题

文档在生成PDF时,文宁都发生了什么。本文讲解了配置对象、resources对象和content对象的作用,以及字体、宇号、坐标、文本摆放等过程。同时,还解释了为什么PDF转word或转文字都是一行一行的以及为什么页眉页脚的问题会加大识别难度。最后提到了文本的编码和PDF中缺少文档结构标…

vue脚手架构建的项目是怎么运行的

前言 我们前面说了在怎么创建并且配置nodejs以及安装脚手架功能之后,我们进一步就是对应的运行脚手架搭建的项目 脚手架项目介绍 对应的我们一般都是vscode的右下角的npm脚本去执行对应的数据文件,然后等待项目构建之后就可以打开了。 我现在习惯的是…

Log4j2-24-log4j2 相同的日志打印 2 次

现象 相同的日志打印了两次,且因为日志的配置不同,导致脱敏的情况不一致。 代码与配置 代码 package com.ryo.log4j2.cfg.additivity;import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;public class SimpleDemo…

生产工业数据采集分析——大数据生产基石!

关键词:工业数据采集分析,工业数据,工业数据采集分析系统,定制数据采集系统 在生产线中,引入使用了各种智能化的仪器与设备,这些设备有些是纯机械式,但有很多智能设备会产出大量的数据,因此,如何从这些大量…

格式工厂怎么转换视频方向

格式工厂因为其免费、操作简单、功能齐全的多重优势,深受大家的喜欢。格式工厂具有可以转换视频、音频、去水印、转换GIF、图片转换、PDF合并、PDF转换等功能,然而在对视频进行剪辑的时候,往往会发现找不到格式工厂的转换视频方向的功能&…

【Docker Registry】docker 镜像仓库实战

Docker Registry 镜像仓库 (Docker Registry) 负责存储、管理和分发镜像,并且提供了登录认证能力,建立了仓库的索引。 镜像仓库管理多个 Repository, Repository 通过命名来区分。每个 Repository 包含一个或多个镜像,镜像通过镜…