JAVA多线程(5)

JAVA多线程(5)

线程安全问题概述

卖票问题分析

  • 单窗口卖票

img

一个窗口(单线程)卖100张票没有问题
单线程程序是不会出现线程安全问题的

  • 多个窗口卖不同的票

img

3个窗口一起卖票,卖的票不同,也不会出现问题
多线程程序,没有访问共享数据,不会产生问题

  • 多个窗口卖相同的票

img

3个窗口卖的票是一样的,就会出现安全问题
多线程访问了共享的数据,会产生线程安全问题

线程安全问题代码实现

模拟卖票案例

创建3个线程,同时开启,对共享的票进行出售

public class Demo01Ticket {
 public static void main(String[] args) {
 //创建Runnable接口的实现类对象
 RunnableImpl run = new RunnableImpl();
 //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
 Thread t0 = new Thread(run);
 Thread t1 = new Thread(run);
 Thread t2 = new Thread(run);
 //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
 }
}
public class RunnableImpl implements Runnable{
 //定义一个多个线程共享的票源
 private int ticket = 100;
 //设置线程任务:卖票
 @Override
 public void run() {
 //使用死循环,让卖票操作重复执行
 while(true){
 //先判断票是否存在
 if(ticket>0){
 //提高安全问题出现的概率,让程序睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,卖票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
 }
 }
 }
}

线程安全问题原理分析

img

线程安全问题产生原理图

分析:线程安全问题正常是不允许产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权;让其他的线程只能等待,等待当前线程卖完票,其他线程在进行卖票。

解决线程安全问题办法1-synchronized同步代码块

同步代码块:synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

使用synchronized同步代码块格式:

synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

代码实现如下:

public class Demo01Ticket {
 public static void main(String[] args) {
 //创建Runnable接口的实现类对象
 RunnableImpl run = new RunnableImpl();
 //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
 Thread t0 = new Thread(run);
 Thread t1 = new Thread(run);
 Thread t2 = new Thread(run);
 //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
 }
}
public class RunnableImpl implements Runnable{
 //定义一个多个线程共享的票源
 private int ticket = 100;
 //创建一个锁对象
 Object obj = new Object();
 //设置线程任务:卖票
 @Override
 public void run() {
 //使用死循环,让卖票操作重复执行
 while(true){
 //同步代码块
 synchronized (obj){
 //先判断票是否存在
 if(ticket>0){
 //提高安全问题出现的概率,让程序睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,卖票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
 }
 }
 }
 }
}

⚠️注意:

  1. 代码块中的锁对象,可以使用任意的对象。
  2. 但是必须保证多个线程使用的锁对象是同一个。
  3. 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行。

同步技术原理分析

同步技术原理:

使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器

3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票。

  • t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块这时t0会检查synchronized代码块是否有锁对象

发现有,就会获取到锁对象,进入到同步中执行

  • t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块这时t1会检查synchronized代码块是否有锁对象

发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象,t0线程执行完同步中的代码,会把锁对象归 还给同步代码块t1才能获取到锁对象进入到同步中执行

总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。

解决线程安全问题办法2-synchronized普通同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:

public synchronized void payTicket(){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

代码实现:

 public /**synchronized*/ void payTicket(){
 synchronized (this){
 //先判断票是否存在
 if(ticket>0){
 //提高安全问题出现的概率,让程序睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,卖票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
 }
 }
 }

分析:

定义一个同步方法,同步方法也会把方法内部的代码锁住,只让一个线程执行。

同步方法的锁对象是谁?

就是实现类对象 new RunnableImpl(),也是就是this,所以同步方法是锁定的this对象。

解决线程安全问题办法3-synchronized静态同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
格式:

public static synchronized void payTicket(){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}

代码实现:

 public static /**synchronized*/ void payTicketStatic(){
 synchronized (RunnableImpl.class){
 //先判断票是否存在
 if(ticket>0){
 //提高安全问题出现的概率,让程序睡眠
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 //票存在,卖票 ticket--
 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
 }
 }
 }

分析:

静态的同步方法锁对象是谁?

不能是this,this是创建对象之后产生的,静态方法优先于对象

静态方法的锁对象是本类的class属性–>class文件对象(反射)。

解决线程安全问题办法4-Lock锁

Lock接口中的方法:

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁

使用步骤:

  1. 在成员位置创建一个ReentrantLock对象
  2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

代码实现:

public class RunnableImpl implements Runnable{
 //定义一个多个线程共享的票源
 private int ticket = 100;
 //1.在成员位置创建一个ReentrantLock对象
 Lock l = new ReentrantLock();
 //设置线程任务:卖票
 @Override
 public void run() {
 //使用死循环,让卖票操作重复执行
 while(true){
 //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
 l.lock();
 try {
 //先判断票是否存在
 if(ticket>0) {
 //提高安全问题出现的概率,让程序睡眠
 Thread.sleep(10);
 //票存在,卖票 ticket--
 System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
 }
 } catch (InterruptedException e) {
 e.printStackTrace();
 }finally {
 l.unlock();
 //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
 //无论程序是否异常,都会把锁释放
 }
 }
 }

分析

java.util.concurrent.locks.Lock接口

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。

2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

公平锁、非公平锁的创建方式:

//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
 //创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);

3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

ReentrantLock和Synchronized的区别

相同点:

  1. 它们都是加锁方式同步;
  2. 都是重入锁;
  3. 阻塞式的同步;也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善);

不同点:

ck对象可以同时绑定多个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

ReentrantLock和Synchronized的区别

相同点:

  1. 它们都是加锁方式同步;
  2. 都是重入锁;
  3. 阻塞式的同步;也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善);

不同点:

img

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

度加创作工具 演示

度加创作工具 功能图功能测试文比润色测试经验分享测试测试输出测试输出工具地址功能图 功能测试 文比润色测试 经验分享测试 测试输出 在人工智能领域,我们一直在追求一个终极目标:让机器能够像人类一样,能够理解、学习和解决各种复杂问题。而要实现这个目标,我们需要将…

【Java 进阶篇】JQuery 事件绑定之事件切换:让页面动起来

欢迎来到这个充满动感的 JQuery 事件绑定之旅!在这篇博客中,我们将深入研究 JQuery 中的事件切换,让你的页面焕发出活力和互动。无论你是前端小白还是有一定经验的开发者,相信这篇文章都会对你有所帮助。 走进事件切换的奇妙世界…

python中Thread实现多线程任务

目录 多线程概括: 使用 Thread 模块创建线程 如果不使用多线程: 多线程概括: 多线程是一种并发执行的编程方式,允许程序同时执行多个独立的线程,每个线程在程序中运行独立的任务。每个线程都是程序的基本执行单元&a…

坐标系下的运动旋量转换

坐标系下的运动旋量转换 文章目录 坐标系下的运动旋量转换前言一、运动旋量物体运动旋量空间运动旋量 二、伴随变换矩阵三、坐标系下运动旋量的转换四、力旋量五、总结参考资料 前言 对于刚体而言,其角速度可以写为 ω ^ θ ˙ \hat {\omega} \dot \theta ω^θ˙&…

【2017年数据结构真题】

请设计一个算法,将给定的表达式树(二叉树)转换成等价的中缀表达式(通过括号反映次序),并输出。例如,当下列两棵表达式树作为算法的输入时: 输出的等价中缀表达式分别为(ab)(a(-d)) 和…

【Java 进阶篇】Ajax 入门:打开前端异步交互的大门

欢迎来到前端异步交互的世界!在这篇博客中,我们将深入探讨 Ajax(Asynchronous JavaScript and XML),这是一项能够让你的网页在不刷新的情况下与服务器进行数据交互的技术。无论你是刚刚踏入前端开发的小白,…

PMCW体制雷达系列文章(4) – PMCW雷达之抗干扰

说明 本文作为PMCW体制雷达系列文章之一,主要聊聊FMCW&PMCW两种体制雷达的干扰问题。事实上不管是通信领域还是雷达领域,对于一切以电磁波作为媒介的信息传递活动,干扰是无处不在的。近年来,随着雷达装车率的提高,…

Python---练习:封装一个函数,用于生成指定长度的验证码

练习涉及相关链接:Python---练习:编写一段Python代码,生成一个随机的4位验证码-CSDN博客 Python----函数中的说明文档-CSDN博客Python---return返回值-CSDN博客 代码: # 定义一个generate_code()函数 def generate_code(num): …

SDUT OJ《算法分析与设计》搜索算法

A - 子集和问题 Description 子集和问题的一个实例为〈S,t〉。其中,S{ x1 , x2 ,…,xn }是一个正整数的集合,c是一个正整数。子集和问题判定是否存在S的一个子集S1,使得: 。 试设计一个解子…

【Web】Ctfshow SSTI刷题记录1

目录 ①web361 362-无过滤 ②web363-过滤单双引号 ③web364-过滤单双引号和args ④web365-过滤中括号[]、单双引号、args ⑤web366-过滤单双引号、args、中括号[]、下划线 ⑦web367-过滤单双引号、args、中括号[]、下划线、os ⑧web368-过滤单双引号、args、中括号[]、下…

【嵌入式 – GD32开发实战指南(ARM版本)】第2部分 外设篇 - 第3章 温度传感器DS18B20

1 理论分析 1.1 DS18B20概述 DS18B20 是 DALLAS 最新单线数字温度传感器,新的"一线器件"体积更小、适用电压更宽、更经济。Dallas 半导体公司的数字化温度传感器 DS1820 是世界上第一片支持 "一线总线"接口的温度传感器。 DS18B20采用的单总线协议,也…

Centos7 重置 Root 密码

Centos7 重置 Root 密码 1.启动服务器2.编辑启动项3.修改密码4.重新登陆 1.启动服务器 启动服务器后,不要直接进入系统,在开机页面按键盘【E】 2.编辑启动项 按【E】后进入如下页面,并按向下箭头,找到如图位置,添加如…

数据结构:红黑树的插入实现(C++)

个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C》《Linux》 文章目录 一、红黑树二、红黑树的插入三、代码实现总结 一、红黑树 红黑树的概念: 红黑树是一颗二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色&…

慢日志查询

概述 MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过 long_query_time 值的SQL,则会被记录到慢查询日志中,ong_query_time 的默认值为 10,意思是运行10S…

redis三种集群方式

redis有三种集群方式:主从复制,哨兵模式和集群。 1.主从复制 主从复制原理: 从服务器连接主服务器,发送SYNC命令; 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所…

python-opencv 培训课程笔记(2)

python-opencv 培训课程笔记(2) 1.图像格式转换 先看一下cvtColor函数的例子 #默认加载彩图 pathrD:\learn\photo\cv\cat.jpg# imread(path,way) #way0 灰度图。way1 彩图 #默认彩图 imgcv2.imread(path) img_dogcv2.imread(path_dog) #图片格式的转化…

【开发流程】持续集成、持续交付、持续部署

一、开发工作流程 假设把开发流程分为以下几个阶段: 编码 -> 构建 -> 集成 -> 测试 -> 交付 -> 部署 如上图所示,持续集成、持续交付、持续部署有着不同的软件自动交付周期。 二、持续集成、持续交付、持续部署 1、持续集成 持续集成…

FPGA设计时序约束八、others类约束之Set_Case_Analysis

目录 一、序言 二、Set Case Analysis 2.1 基本概念 2.2 设置界面 2.3 命令语法 2.4 命令示例 三、工程示例 四、参考资料 一、序言 在Vivado的时序约束窗口中,存在一类特殊的约束,划分在others目录下,可用于设置忽略或修改默认的时序…

关于DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC的一些发现

任务在哪 这个是11g以后的自动收集统计信息的后台任务,10g之前是在dba_scheduler_jobs里查看 SQL> SELECT CLIENT_NAME ,STATUS ,MEAN_INCOMING_TASKS_7_DAYS,MEAN_INCOMING_TASKS_30_DAYS FROM DBA_AUTOTASK_CLIENT WHERE…

mac控制台命令小技巧

shigen日更文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。 hello伙伴们,作为忠实的mac骨灰级别的粉丝,它真的给我带来了很多效率上的提升。那作为接…