JAVA多线程(5)

news/2024/11/24 6:53:42/

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;//设置线程任务:卖票@Overridepublic 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();//设置线程任务:卖票@Overridepublic 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();//设置线程任务:卖票@Overridepublic 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


http://www.ppmy.cn/news/1228010.html

相关文章

linux文件IO

文件IO截断 截断对文件的偏移量没有影响。

YOLOv8改进 | DAttention (DAT)注意力机制实现极限涨点

论文地址: DAT论文地址 官方地址:官方代码的地址 代码地址:文末有修改了官方代码BUG的代码块复制粘贴即可 一、本文介绍 本文给大家带来的是YOLOv8改进DAT(Vision Transformer with Deformable Attention)的教程,其发布于2022…

基于springboot实现家政服务管理平台项目【项目源码+论文说明】计算机毕业设计

摘要 随着家政服务行业的不断发展,家政服务在现实生活中的使用和普及,家政服务行业成为近年内出现的一个新行业,并且能够成为大众广为认可和接受的行为和选择。设计家政服务管理平台的目的就是借助计算机让复杂的销售操作变简单,…

C/C++通过位操作实现2个uint32_t合并为uint64_t

#include <iostream> using namespace std;int main() {uint32_t a 10;uint32_t b 600;//先将uint32_t的a转为uint64_t&#xff0c;此时a前面32位都是0&#xff0c;然后左移32位&#xff0c;此时右32位为0&#xff0c;最后加上uint32_t类型的b&#xff0c;填充右32位的…

蓝桥杯每日一题2023.11.19

题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 首先想到的方法为dfs去寻找每一个数&#xff0c;但发现会有超时 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, cnt, a[N]; void dfs(int dep, int sum, int start) {if(dep 4){if(s…

【数据库表及字段统计SQL】【mysql】【clickhouse】【oracle】

最近在一些元数据整理时&#xff0c;需要对数据库进行澄清&#xff0c;奈何数据库没有专门的运维工程师&#xff0c;得自行取相关信息&#xff0c;故最终整理了相关统计语句。 Clickhouse 元数据明细 SELECTt1.database AS 库名,t1.name AS 表名,replaceRegexpAll(toStri…

【装机】第一次装机记录

本篇文章记录第一次装机的过程。 配置 部件型号CPUAMD 锐龙 R5 7500F主板华硕 TUF GAMING A620M-PLUS显卡耕升 RTX4070 踏雪内存金百达 黑刃 DDR5 16G/32G 6000硬盘铠侠 2TB EXCERIA Pro SE10 极至超速系列电源微星 MAG A650BN散热利民 AX120 R SE AGHP逆重力热管支持LGA1700…

让你彻底学会HBase

让你彻底学会HBase Apache HBase&#xff08;Hadoop DataBase&#xff09;是一个开源的、高可靠性、高性能、面向列&#xff08;这里指列族&#xff0c;非列式存储&#xff09;、可伸缩、实时读写的分布式数据库。利用 Hadoop HDFS 作为其文件存储系统&#xff0c;利用 ZooKee…