【多线程 (二)】线程安全问题、同步代码块、同步方法、Lock锁、死锁

news2024/11/15 3:48:48

文章目录

  • 线程安全问题
  • 前言
    • 2.1多线程模拟卖票出现的问题
    • 2.2卖票案例中出现的问题分析
    • 2.3同步代码块解决数据安全问题
    • 2.4同步方法解决数据安全问题
    • 2.5Lock锁
    • 2.6死锁
    • 总结

线程安全问题

前言

之前我们讲了多线程的基础知识,但是在我们解决实际问题中会遇到一些错误,这些错误是怎么产生的呢?又该如何解决呢?今天我们来学习线程安全问题,下面来看一个生活中常见的卖票问题,我们用多线程实现。

2.1多线程模拟卖票出现的问题

  • 案例需求
    某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

  • 实现步骤

    • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下
    • 判断票数大于0,就卖票,并告知是哪个窗口卖的
    • 卖了票之后,总票数要减1
    • 票卖没了,线程停止
    • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    • 创建SellTicket类的对象
    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    • 启动线程
  • 代码实现

public class SellTicket implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while(true){
            if(ticket==0){
                break;
            }else{
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName()+"在卖票,还剩下"+ticket+"张票");

            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st,"窗口一");
        Thread t2 = new Thread(st,"窗口二");
        t1.start();
        t2.start();
    }
}
  • 运行结果
    在这里插入图片描述
    在这里插入图片描述

2.2卖票案例中出现的问题分析

从运行结果我们可以看出,当线程二买了一张票后,剩余99张票,之后线程一买了一张票后也打印剩余99张票,还会打印出负数票的情况,显然,这是不符合生活常识的,看代码,我们明明在每次线程结束都会使剩余票数减一,也会判断当剩余票数为零时,跳出,线程会处于销毁状态,但是为什么还会出现这些情况呢?我们来分析一下

  • 打印相同票数问题
    在阅读分析前,希望读者熟悉一下我上述的代码,代码中在测试类我创建了两个线程对象,然后开启两个线程,这时Java虚拟机会帮我们调用实现类中的run()方法,我们都知道,在任意时刻,每个线程都有可能抢夺CPU 的使用权,我们先假设线程一刚开始抢夺到了CPU的使用权,并开始执行 run()方法,当执行到 sleep()方法时,线程一休眠,线程二此时抢到了CPU的使用权,并开始执行 run()方法,此时线程一和线程二都在执行run()方法,线程二执行到 sleep()方法时,开始休眠,线程一此时抢夺到CPU的使用权,线程一继续执行run()方法,执行到 ticket- -;此时票数剩余99,而线程一和线程二共享的一个数据,线程二的 ticket 属性从 100 变成 99,当线程一准备执行打印方法时,线程二又抢夺到CPU的使用权,继续执行到 ticket- -;此时线程一和线程二中的 ticket属性都为 98,线程二执行打方法,打印 98,线程一抢到CPU的使用权,执行打印方法打印 98,这也就是为什么会出现打印相同票数的原因了。

  • 打印负数票的问题
    同样,当线程一线程二执行了好多次以后,此时,线程一和线程二都回到原来的起点开始抢夺CPU使用权,前提是ticket此时等于1,当线程一抢夺到CPU的执行权时,开始执行run()方法体中的代码,执行到 ticket- -;,此时的 ticket 等于 0,当线程一准备打印剩余票数时,CPU执行权又被线程二抢夺,线程二开始执行run()方法,此时线程二的 ticket 属性值等于 0,当线程二执行到 ticket - -;时,此时线程一和线程二的 ticket 属性值都会自减一,此时 ticket 属性值从 0 变成 -1,接着线程一抢夺到CPU使用权,打印 -1,线程二随后抢到CPU执行权,也打印 -1,这就是为什么会出现负数票的原因了。

2.3同步代码块解决数据安全问题

  • 首先我们总结一下上述安全问题出现的条件
    • 1.是多线程环境
    • 共享数据
    • 多条语句操作共享数据
  • 那我们如何解决多线程安全问题呢?
    • 基本思想:让程序没有安全问题的环境
  • 怎么实现呢?
    • 把多条语句操作共享数据的代码给起来,让任意时刻只能有一个线程执行即可。
    • Java 提供了同步代码块的方式来解决。
  • 同步代码块格式:
synchronized(任意对象) { 
	多条语句操作共享数据的代码 
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁。

  • 同步的好处弊端
    • 好处:解决了多线程的数据安全问题。
    • 弊端:当线程很多时,因为每个线程都会去判断 同步上的,这是很耗费资源的,无形中会降低程序的运行效率
  • 代码演示
public class Ticket2 implements Runnable {
    private int ticket = 100;
    private Object obj= new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {//多个线程必须使用同一把锁
                if (ticket == 0) {
                    //卖完了
                    break;
                } else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            }
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();

        //多线程在执行每一行代码的时候,它 cpu 的执行权都有可能被抢走
        //线程一第一次抢到执行权,但在打印99之前还没有打印呢它cpu的执行权被二跟三抢走了

    }
}

  • 运行结果
    在这里插入图片描述
    在这里插入图片描述
    补充:打印票数不是按照降序顺序打印也是由于在任意时刻,每个线程都是有可能抢到CPU的使用权造成的,这种情况也可以通过加锁的方式解决。

2.4同步方法解决数据安全问题

在解决安全问题时也可以通过同步方法的方式,原理也是通过加锁

  • 同步方法的格式
    • 同步方法:就是把 synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
	 方法体; 
}
  • 同步方法的锁对象是什么呢?

    • this
  • 同步静态方法的锁对象是什么呢?

    • 类名.class
  • 代码演示

public class MyRunnable implements Runnable { 
	private static int ticketCount = 100; 
	@Override 
	public void run() {
		 while(true){ 
		 	if("窗口一".equals(Thread.currentThread().getName())){
		 		 //同步方法 
		 		 boolean result = synchronizedMthod(); 
		 		 if(result){ 
		 		 	break; 
		 		 } 
		 	}
		 	if("窗口二".equals(Thread.currentThread().getName())){
		 		//同步代码块 
		 		synchronized (MyRunnable.class){
		 			if(ticketCount == 0){ 
		 				break; 
		 			}else{
		 				try {Thread.sleep(10);
		 				} catch (InterruptedException e) {
		 				 e.printStackTrace(); 
		 				}
		 				ticketCount‐‐;
		 				System.out.println(Thread.currentThread().getName() + "在卖票,还剩 下" + ticketCount + "张票"); 
		 			} 
		 		} 
		 	}
		 } 
	}
	private static synchronized boolean synchronizedMthod() {
		if(ticketCount == 0){ 
			return true; 
		}else{
			try {
				Thread.sleep(10); 
				} catch (InterruptedException e) { 
					e.printStackTrace(); 
				}
				ticketCount‐‐;
				System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票"); 
				return false; 
			} 
		} 
	}
public class Demo { 
	public static void main(String[] args) { 
		MyRunnable mr = new MyRunnable(); 
		Thread t1 = new Thread(mr); 
		Thread t2 = new Thread(mr); 
		t1.setName("窗口一"); 
		t2.setName("窗口二"); 
		t1.start(); 
		t2.start(); 
	} 
}

2.5Lock锁

虽然我们可以理解同步代码块同步方法锁对象问题,但是我们并没有直接看到哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

  • ReentrantLock构造方法
方法名说明
ReentrantLock()创建一个 ReentrantLock的实例
  • 加锁解锁方法
方法名说明
void lock()获得锁
void unlock()释放锁
  • 代码演示
public class Ticket implements Runnable { 
	//票的数量 
	private int ticket = 100; 
	private Object obj = new Object(); 
	private ReentrantLock lock = new ReentrantLock(); 
	@Override 
	public void run() { 
		while (true) { 
		//synchronized (obj){//多个线程必须使用同一把锁. 
			try {
				lock.lock(); 
				if (ticket <= 0) { 
					//卖完了 
					break; 
				} else { 
					Thread.sleep(100); 
					ticket‐‐;
					 System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
				} 
			} catch (InterruptedException e) { 
				e.printStackTrace(); 
			} finally { 
				lock.unlock(); 
			}
			// } 
		} 
	} 
}
public class Demo { 
	public static void main(String[] args) { 
		Ticket ticket = new Ticket(); 
		Thread t1 = new Thread(ticket); 
		Thread t2 = new Thread(ticket); 
		Thread t3 = new Thread(ticket); 
		t1.setName("窗口一"); 
		t2.setName("窗口二"); 
		t3.setName("窗口三"); 
		t1.start(); 
		t2.start();
		t3.start(); 
	} 
}

2.6死锁

  • 概述
    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
    举个例子:
    在这里插入图片描述
    如图:小明和小红是两个线程,它们的目的都是前往淑芳阁。

  • 正常情况
    在这里插入图片描述
    白色路障代表通用的,小明先抢夺到CPU的使用权,此时锁默认是打开的,等小明经过路障后,将锁关闭,此时就算小红就算抢到CPU的执行权,也只能等到小明到达淑芳阁后打开锁,小红才能去淑芳阁,这是比较正常的情况。

  • 死锁情况
    在这里插入图片描述
    另一种情况如图,小明和小红都有自己的锁,白色代表小明的锁,黑色代表小红的锁,刚开始两个锁都是默认打开的,小红抢到CPU的执行权,此时因为小明还没开始,所以黑色锁默认打开,小红直接进去,然后因为小红已经开始执行,黑色锁关闭,正当小红要经过小明的白色锁时,小明抢到了CPU的执行权,然后经过自己的锁,并关闭自己的白色锁,此时两个锁处于关闭状态,所以小明跟小红都被困在里边了。这就是我们所说的死锁

  • 什么情况会产生死锁

    • 1.资源有限
    • 2.同步嵌套
  • 代码演示

public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();
         new Thread(()->{
            while(true){
                synchronized (objA){
                    //线程一
                    synchronized (objB){
                        System.out.println("小明正在走路");
                    }
                }
            }
         }).start();

         new Thread(()->{
             while(true){
                 synchronized (objB){
                     //线程二
                     synchronized (objA){
                         System.out.println("小红正在走路");
                     }
                 }
             }
         }).start();
    }
}
  • 运行结果
    在这里插入图片描述

总结

本篇文章通过模拟现实中的卖票案例引出了多线程中可能存在的问题,并讲述了通过同步代码块以及同步方法加锁的方式解决卖票案例中的问题,也简单介绍了JDK5之后,可以通过实例Lock锁的实现类 ReentrantLock让我们能够更清晰的看到在哪里上锁,又在哪里释放锁,也分析了线程死锁问题产生的原因,希望大家多多支持,你们的支持,是我更新的动力!在这里插入图片描述

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

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

相关文章

接口自动化测试实战之智能场景如何攻破

智能场景的意思就是怎么样才能让接口自动化智能化&#xff0c;让使用接口框架的人越来越没有要求&#xff0c;大街上随便拉一个人来&#xff0c;一分钟了解框架的使用&#xff0c;就能完美地去完成接口自动化测试。 1.找出公司要求我们测试的接口的共同点 假设有10个接口&…

【附源码】计算机毕业设计JAVA移动电商网站

【附源码】计算机毕业设计JAVA移动电商网站 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA mybati…

(一)进程与线程

黑马程序员深入学习Java并发编程&#xff0c;JUC并发编程全套教程_哔哩哔哩_bilibili 一、进程与线程&#xff08;P5&#xff09; 1. 进程 &#xff08;1&#xff09;程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至…

查阅标准文档以及effective c++作者文笔 真正搞懂万能引用和引用折叠以及完美转发

在解释任何东西以前 我都必须要强调 我们为什么需要这个东西 如果一个东西我们都是不需要的 那么我们解释他干嘛? 假定你彻底了解了一个东西 但是你并不知道你为什么需要他 他能解决什么问题 那你仅仅就只是背了一段理论性的东西 对于你本人的成长毫无用处 这里我们一次性讲懂…

sqli-labs/Less-58

这一关只有五次机会了 哎怎么办啊 那就只能找出每轮的共同点 这一关肯定不能一轮就完成所有的操作 至少得分个两轮进行操作才可以 前一轮进行注入类型的获取 后一轮进行各种爆破操作 分配好了 首先去判断一下注入类型是否属于数字型注入 输入如下 id1 and 12 回显如下 不属于…

Web大学生网页作业成品 基于HTML+CSS+JavaScript---个人介绍5页 带视频 带报告

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | ‍个人博客网站 | ‍个人主页介绍 | 个人简介 | 个人博客设计制作 | 等网站的设计与制作 | 大学生个人HTML网页设计作品 | HTML期末大学生网页设计作业…

大规模 Spring Cloud 微服务无损上下线探索与实践

作者&#xff1a;十眠 “从一次常见的发布说起&#xff0c;在云上某个系统应用发布时&#xff0c;重启阶段会导致较大数量的 OpenAPI、上游业务的请求响应时间明显增加甚至超时失败。随着业务的发展&#xff0c;用户数和调用数越来越多&#xff0c;该系统又一直保持一周发布二…

CAD特殊符号,你不一定会!!!

在CAD软件中&#xff0c;有时候会输入一些特殊的符号。比如在标明高低差的时候会输入“”号&#xff0c;在标明管子或者钢筋的直径为输入直径符号“”&#xff0c;为了标明角度值需要输入符号“”&#xff0c;那么这些符号怎么快速的绘制出来呢&#xff1f;我们一起用CAD梦想画…

专利解析|多维建模结合AI识别商品特征的方法

企业采购数字化转型的背景 国家“十四五”规划纲要提出要推进产业数字化转型&#xff0c;在供给侧结构性改革大背景下&#xff0c;国家出台了《企业数字化采购实施指南》&#xff0c;大大促进了企业采购电商化的发展。企业电商化采购能提高企业的采购效率、加快物流速度、降低…

m基于QPSK调制解调的无线图像传输matlab仿真,包括扩频解扩均衡等模块

目录 1.算法描述 2.仿真效果预览 3.MATLAB部分代码预览 4.完整MATLAB程序 1.算法描述 软件无线电在无线通信领域被称为是自模拟通信过渡到数字通信之后的又一次革命&#xff0c;在军用和民用方面都有着广阔的应用。它是一种新的无线通信技术&#xff0c;基于通用的可编程的…

【JAVA高级】——封装JDBC中的DaoUtils工具类(Object类型方法)

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

【文献整理】基于深度强化学习的知识图谱推理研究

目录DeepPath背景Core贡献几个要点&#xff1a;Training pipeline结论DIVINE背景Core贡献预备知识DIVINE推理过程模型文献整理基于综述论文&#xff1a;基于深度强化学习的知识推理研究进展综述_宋浩楠&#xff0c;赵刚&#xff0c;孙若莹 文中对知识图谱推理进行如下分类&…

SpringSecurity(十七)---OAuth2的运行机制(下)-实现一个简单的单点登录应用程序

一、前言 本章实现第一个使用带有Spring Boot和Spring Security 的OAuth2框架的应用程序。这个示例将展示如何将OAuth2应用到Spring Security中&#xff0c;并阐释你需要了解的一些接口的内容。顾名思义&#xff0c;单点登录&#xff08;SSO&#xff09;应用程序是通过授权服务…

如何使用一台电脑远程控制多台电脑

如今&#xff0c;远程控制软件已经广泛应用于我们的日常生活中。我们使用远程桌面软件远程控制另一台电脑来完成我们的工作和学习。在某些情况下&#xff0c;我们可能还需要同时远程控制多台电脑。例如&#xff1a; 您是一名培训师&#xff0c;正在寻找远程访问软件来同时远程…

[激光原理与应用-15]:《激光原理与技术》-1- 什么是激光,激光概述

目录 第1章 什么是激光 1.1 什么是激光 1.2激光在生活中应用 第2章 激光的特点 2.1 方向性好&#xff08;平行性、直线性&#xff09; 2.2 单色性好&#xff08;颜色纯度高&#xff09; 2.3 相干性比太阳光好 2.4 亮度高 2.5 能量极大 第3章 光产生的方式与核心概念 …

又爆冷了啦,日本半场逆转德国,怎么利用共享经济搅乱世界杯格局

近日世界杯热点逐渐升高&#xff0c;在23号晚上亚洲劲旅日本以2-1逆转多次捧得大力神杯的德国队&#xff0c;此前德国还从未输过日本队&#xff0c;因此德国再次吃到闭门羹&#xff0c;爆出了本届世界杯开赛以来既阿根廷惨败的又一大冷门。赛后&#xff0c;日本全国人民共同庆祝…

Web(二)html5基础-超链接的应用(知识训练和编程训练)

web知识训练_html5_超链接的应用 web编程训练_html5_超链接的应用 第1关_创建热字超链接 编程要求 在右侧编辑器中的Begin - End区域内补充代码&#xff0c;创建热字超链接&#xff0c;具体要求是&#xff1a; 1.链源文字为“听音乐找酷我”。 2.链宿地址为“https://www.ku…

FPGA——多路选择器实现按键控制LED灯的亮灭

文章目录前言一、多路选择器二、绘制模块框图及波形图三、Verilog HDL代码及测试代码四、创建工程五、仿真六、上板验证1、分配引脚2、烧录七、效果演示八、总结前言 软件&#xff1a;Quartus Prime Standard 18.0仿真软件&#xff1a;modelsim 10.5代码编写软件&#xff1a;V…

【虹科新品】 HK-MR430330绝对式光纤编码器介绍合集(下)

HK-MR430系列ZapFREE光纤位置传感器是一款外形小巧、具有13位单圈分辨率的旋转位置传感器。MR430设计新颖&#xff0c;开发了新的应用和OEM产品功能&#xff0c;这在以前的电子传感器是无法实现的。该传感器100%无源&#xff0c;不受EMI、RFI、微波和磁场的影响。创新型全绝缘设…

我参加NVIDIA Sky Hackathon 后端修改

文件架构 前面两个分别是执行语音识别和图片识别的代码templates 存放的是网页的模板&#xff0c; 前端将文件写在这里即可uploads 存放的是上传至后台的文件server.ipynb 用于启动 flash 服务器app.py 内是用 flash 写的 Python 后端install_tools.sh 是用于安装相关工具的 sh…