当前位置: 首页 > news >正文

Java多线程——同步

同步是什么?

当两个线程同时对一个变量进行修改时,不同的访问顺序会造成不一样的结果,这时候就需要同步保证结果的唯一性。

未同步时

新建Bank类,transfer()用于在两个账户之间转账金额

class Bank {private double[] accounts;public Bank(int accountNum, double initialMoney) {accounts = new double[accountNum];Arrays.fill(accounts, initialMoney);}public void transfer(int from, int to, double money) {if (accounts[from] < money) {return;}System.out.print(Thread.currentThread());accounts[from] -= money;System.out.printf("%10.2f from %d to %d", money, from, to);accounts[to] += money;System.out.printf(" Total %10.2f%n", getTotal());}public double getTotal() {double total = 0.0d;for (double temp : accounts) {total += temp;}return total;}public int size() {return accounts.length;}
}

假设银行有1000个开户人,每个人账户有1000元,新建1000个线程进行随机转账,无论怎么转账,总金额都应该为1000000,但实际的钱却越来越少(这个例子不太好,原因是浮点数加减可能误差)

int NACCOUNTS = 1000;
double INITIAL_BALANCE = 1000;
double MAX_AMOUNT = 1000;
int DELAY = 10;Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
for (int i = 0; i < NACCOUNTS; i++) {int fromAccount = i;new Thread(new Runnable() {@Overridepublic void run() {try {while (true) {int toAccount = (int) (bank.size() * Math.random());double money = MAX_AMOUNT * Math.random();bank.transfer(fromAccount, toAccount, money);Thread.sleep((int) (DELAY * Math.random()));}} catch (InterruptedException e) {e.printStackTrace();}}}).start();
}

原因分析:

  • 当两个线程同时执行到accounts[to] += money;
  • 线程1取出accounts[to](如900)放到寄存器,+money(如100),正准备写回accounts[to](变成了1000)时
  • 线程2抢占到权限执行accounts[to] += money 将accounts[to]修改为了10002
  • 线程1再写就将10002覆盖成了1000

ReentrantLock

修改Bank,对其transfer方法加锁,注意lock应该为成员变量,即每个Bank实例都只有一把锁,不同对象之间的锁不会互相影响,需要在finally释放锁

class Bank {private double[] accounts;private ReentrantLock lock = new ReentrantLock();public Bank(int accountNum, double initialMoney) {accounts = new double[accountNum];Arrays.fill(accounts, initialMoney);}public void transfer(int from, int to, double money) {if (accounts[from] < money) {return;}lock.lock();try {System.out.print(Thread.currentThread());accounts[from] -= money;System.out.printf("%10.2f from %d to %d", money, from, to);accounts[to] += money;System.out.printf(" Total %10.2f%n", getTotal());} finally {lock.unlock();}}public double getTotal() {double total = 0.0d;for (double temp : accounts) {total += temp;}return total;}public int size() {return accounts.length;}
}

Condition

在转账时,应该避免选择没有足够资金的账号转出

if(bank.getMoney(fromAccount) >= money){bank.transfer(fromAccount, toAccount, money);
}

但在多线程情况下,可能两个线程同时进入if,一个线程转账后导致另外一个线程金额不够转账,故我们要确保在检查之后加锁禁止别的线程先行一步

private ReentrantLock lock = new ReentrantLock();
lock.lock();
try {while(account[from]  < money){}
}finally{lock.unlock();
}

但当账户没有钱的时候,转出线程会一直等待其他线程转入资金,而其他线程因为无法拿到锁而无法转入,这就造成了死锁,这时候就需要条件对象,当金额不足时阻塞线程放弃锁的持有

private ReentrantLock lock = new ReentrantLock();
private Condition moneyEnough;lock.lock();
try {moneyEnough = lock.newCondition();while(account[from]  < money){moneyEnough.await();}
}finally{lock.unlock();
}

当其他线程转账后,应该调用signalAll()唤起所有await()中的线程,当其中的某个线程被调度并再次获取锁后,会再进入try子句检测金额是否足够

moneyEnough.signalAll();

Tips:

  • 还有一个signal()方法随机唤起一个等待线程
  • 当所有线程的金额都小于转账金额,调用await(),所有线程都会阻塞,此时会再次死锁

synchronized

Java中每一个对象都有一个内部锁,如果一个方法用synchronized声明,线程调用该方法时需要获得其内部锁,即

public synchronized void method(){}

等价于

private ReentrantLock innerLock = new ReentrantLock();
public void method(){innerLock.lock();try{}finally{innerLock.unlock();}
}

内部锁只有一个条件,对其的阻塞和唤醒调用wait()、notifyAll()/notify(),它们是Object中的final方法,相当对ReentrantLock调用

innerLock.await();
innerLock.signalAll();

将静态方法声明为synchronized,调用该方法时会锁住对应的类,此时其他线程无法调用该类的其他同步静态方法

Tips:

  • 内部锁不能中断一个正在试图获得锁的线程
  • 锁时不能设置超时
  • 只有一个条件

该使用哪种锁机制?

使用synchronized可减少代码的编写,减少出错的几率

使用ReentrantLock+Condition可以自行控制锁的过程,实现多个条件

同步阻塞

使用其他对象的锁来完成原子操作

public class bank{private Object lock = new Object();public void transfer(int from, int to, double money) {synchronized(lock){}}
}

监视器

volatile

若如果只有一两个域可能发生多线程的误写,可对该域声明为volatile,虚拟机和编译器就知道该域是可能被另一个线程并发更新的,但其不能保证原子性

boolean flag;
public void Not(){flag = !flag;
}

如上,不能保证其再读取、翻转和写入时不被中断

final和锁

当把域声明为final时,其他线程对其的读取只能是构造成功后的值,而不会是null

fial Map<String, Double> accounts = new HashMap<>();

原子性

死锁

线程局部变量

当多个线程都要调用Random中的方法生成随机数时,由于Random是加锁的,其他线程就得等待,此时可用TheadLocal辅助类为各个线程提供各自的Random实例

ThreadLocal<Random> threadLocal = ThreadLocal.withInitial(() -> new Random());
threadLocal.get().nextInt();

此外,专门创建多线程随机数的ThreadLocalRandom,其current()方法会返回当前线程的Random类实例

ThreadLocalRandom.current().nextInt();

锁超时

当线程调用tryLock()方法去申请另一个线程的锁时,很有可能发生阻塞,故可在申请时设置时长

private ReentrantLock lock = new ReentrantLock();
try {lock.tryLock(100, TimeUnit.SECONDS);
} catch (InterruptedException e) {e.printStackTrace();
}

读写锁

读写锁可从ReentrantReadWriteLock取出,为所有获取方法加上读锁,为所有修改方法加上写锁

ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock readLock = reentrantReadWriteLock.readLock();
Lock writeLock = reentrantReadWriteLock.writeLock();

为什么弃用stop()和suspend()

stop()方法用来终止线程,并立即释放线程所获得的锁,这会导致对象状态不一致(如钱已被转出,但在转入前stop,会导致数据丢失)

故无法确定什么时候调用stop()是安全的,在希望停止线程的时候应该中断线程,被中断的线程会在安全的时候停止

suspend()方法用来阻塞线程,直至另一个线程调用resume(),当用suspend()挂起一个持有一个锁的线程,则该锁在resume()之前是不可用的。

若此时再用suspend()方法的线程获取该锁,则会死锁,被挂起的锁等待resume()释放,而要resume()则要获取被挂起的锁

相关文章:

Java多线程——同步

同步是什么&#xff1f; 当两个线程同时对一个变量进行修改时&#xff0c;不同的访问顺序会造成不一样的结果&#xff0c;这时候就需要同步保证结果的唯一性。 未同步时 新建Bank类&#xff0c;transfer()用于在两个账户之间转账金额 class Bank {private double[] account…...

Vue+NodeJS实现邮件发送

一.邮箱配置 这里以QQ邮箱为例,网易邮箱类似. 设置->账号 二.后端服务搭建 index.js const express require(express) const router require(./router); const app express()// 使用路由文件 app.use(/,router);app.listen(3000, () > {console.log(server…...

Go语言网络编程(socket编程)TCP粘包

1、TCP粘包 服务端代码如下&#xff1a; // socket_stick/server/main.gofunc process(conn net.Conn) {defer conn.Close()reader : bufio.NewReader(conn)var buf [1024]bytefor {n, err : reader.Read(buf[:])if err io.EOF {break}if err ! nil {fmt.Println("read…...

【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…...

PaddleOCR学习笔记3-通用识别服务

今天优化了下之前的初步识别服务的python代码和html代码。 采用flask paddleocr bootstrap快速搭建OCR识别服务。 代码结构如下&#xff1a; 模板页面代码文件如下&#xff1a; upload.html : <!DOCTYPE html> <html> <meta charset"utf-8"> …...

9.8 校招 实习 内推 面经

绿泡*泡&#xff1a; neituijunsir 交流裙 &#xff0c;内推/实习/校招汇总表格 1、校招 | 长安福特2024校园招聘正式启动 校招 | 长安福特2024校园招聘正式启动 2、2023校招总结--SLAM岗位 - 5 2023校招总结--SLAM岗位 - 5 3、校招&实习 | 格灵深瞳2024秋季校园招聘启…...

web前段与后端的区别优漫动游

要了解web前后端的区别&#xff0c;首先必须得清楚什么是web前端和web后端。 web前段与后端的区别 首先&#xff1a;web的本意是蜘蛛网和网的意思&#xff0c;在网页设计中我们称为网页的意思。现广泛译作网络、互联网等技术领域。表现为三种形式&#xff0c;即超文本(hyp…...

局域网ntp服务器设置(windows时间同步服务器NetTime)(ubuntu systemd-timesyncd ntp客户端)123端口、ntp校时

文章目录 背景windows如何配置ntp服务器手动配置配置参数AnnounceFlags和Enabled含义 使用软件配置&#xff08;NetTime&#xff09;实操相关疑问&#xff1a;0.nettime.pool.ntp.org是什么&#xff1f; 注意事项请务必检查windows主机123端口是否已被占用&#xff0c;方法请参…...

【个人博客系统网站】我的博客列表页 · 增删改我的博文 · 退出登录 · 博客详情页 · 多线程应用

【JavaEE】进阶 个人博客系统&#xff08;4&#xff09; 文章目录 【JavaEE】进阶 个人博客系统&#xff08;4&#xff09;1. 增加博文1.1 预期效果1.1 约定前后端交互接口1.2 后端代码1.3 前端代码1.4 测试 2. 我的博客列表页2.1 期待效果2.2 显示用户信息以及博客信息2.2.1…...

安全狗陈奋:数据安全需要建立在传统网络安全基础之上

8月22日-23日&#xff0c;由创业邦主办的“2023 DEMO WORLD 企业开放式创新大会”在上海顺利举行。 作为国内云原生安全领导厂商&#xff0c;安全狗受邀出席此次活动。 本次大会以“拥抱开放”为主题&#xff0c;聚焦开放式创新&#xff0c;通过演讲分享、专场对接、需求发布…...

【Redis】深入探索 Redis 的数据类型 —— 哈希表 hash

文章目录 前言一、hash 类型相关命令1.1 HSET 和 HSETNX1.2 HGET 和 HMGET1.3 HKEYS、HVALS 和 HGETALL1.4 HEXISTS 和 HDEL1.5 HLEN1.6 HINCRBY 和 HINCRBYFLOAT1.7 哈希相关命令总结 二、hash 类型内部编码三、hash 类型的应用场景四、原生&#xff0c;序列化&#xff0c;哈希…...

网络安全应急响应典型案例-(DDOS类、僵尸网络类、数据泄露类)

一、DDOS类事件典型案例 DDOS攻击&#xff0c;即分布式拒绝服务攻击&#xff0c;其目的在于使目标电脑的网络或系统资源耗尽&#xff0c;使服务暂时中断或停止&#xff0c;导致其正常用户无法访问。CC攻击使用代理服务器向受害服务器发送大量貌似合法的请求&#xff08;通常…...

【测试开发】Mq消息重复如何测试?

本篇文章主要讲述重复消费的原因&#xff0c;以及如何去测试这个场景&#xff0c;最后也会告诉大家&#xff0c;目前互联网项目关于如何避免重复消费的解决方案。 Mq为什么会有重复消费的问题? Mq 常见的缺点之一就是消息重复消费问题&#xff0c;产生这种问题的原因是什么呢…...

C++和C#程序语言的区别

一直学习C++和C#,两者之间的区别总结一下 目录 一、两种语言概述 C++语言 C#语言 二、两种语言对比 2.1运行依赖...

CentOS配置Java环境报错-bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件

CentOS配置Java环境后执行java -version时报错&#xff1a; -bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件原因是所使用的jdk的版本和Linux内核架构匹配不上 使用以下命令查看Linux架构&#xff1a; [rootlocalhost ~]# cat /proc/version Linux version 3.1…...

MySQL进阶 —— 超详细操作演示!!!(上)

MySQL进阶 —— 超详细操作演示&#xff01;&#xff01;&#xff01;&#xff08;上&#xff09; 一、存储引擎1.1 MySQL 体系结构1.2 存储引擎介绍1.3 存储引擎特点1.4 存储引擎选择 二、索引2.1 索引概述2.2 索引结构2.3 索引分类2.4 索引语法2.5 SQL 性能分析2.6 索引使用2…...

一条爬虫抓取一个小网站所有数据

一条爬虫抓取一个小网站所有数据 ​ 今天闲来无事&#xff0c;写一个爬虫来玩玩。在网上冲浪的时候发现了一个搞笑的段子网&#xff0c;发现里面的内容还是比较有意思的&#xff0c;于是心血来潮&#xff0c;就想着能不能写一个Python程序&#xff0c;抓取几条数据下来看看&am…...

八大排序——快速排序

Hello&#xff0c;大家好&#xff0c;今天分享的八大排序里的快速排序&#xff0c;所谓快速排序是一个叫霍尔的人发明&#xff0c;有很多人可能会觉得为什么不叫霍尔排序&#xff0c;其中原因就是因为它快&#xff0c;快速则体现了它的特点&#xff0c;今天我们就来讲一下快速排…...

【ES】笔记-Class类剖析

Class Class介绍与初体验ES5 通过构造函数实例化对象ES6 通过Class中的constructor实列化对象 Class 静态成员实例对象与函数对象的属性不相通实例对象与函数对象原型上的属性是相通的Class中对于static 标注的对象和方法不属于实列对象&#xff0c;属于类。 ES5构造函数继承Cl…...

数学建模--Seaborn库绘图基础的Python实现

目录 1.绘图数据导入 2. sns.scatterplot绘制散点图 3.sns.barplot绘制条形图 4.sns.lineplot绘制线性图 5.sns.heatmap绘制热力图 6.sns.distplot绘制直方图 7.sns.pairplot绘制散图 8.sns.catplot绘制直方图 9.sns.countplot绘制直方图 10.sns.lmplot绘回归图 1.绘图数…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...