多线程原子性、一致性与有序性
作者:
逍遥Sean
简介:一个主修Java的Web网站\游戏服务器后端开发者
主页:https://blog.csdn.net/Ureliable
觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言!
前言:
多线程原子性、一致性和有序性是指在多线程编程中,保证数据正确性和程序执行顺序的三个重要概念。
- 原子性:原子操作是指不可中断的一个操作,要么全部执行成功,要么全部执行失败,中间不会被其他线程干扰。多线程环境下,如果多个线程同时更新一个共享变量,就可能出现问题。原子性的解决方案包括使用原子类、锁和同步机制等。
- 一致性:一致性是指对于多个线程之间共享的数据,操作后数据的状态保持一致。在多线程环境下,如果多个线程访问同一个共享变量,并发修改它,就可能出现数据不一致的情况。一致性的解决方案包括使用锁和同步机制等。
- 有序性:有序性是指多线程环境下,保证程序执行顺序的一种机制。在多线程环境下,程序执行顺序不是按照代码顺序执行的,可能会因为CPU调度、指令重排等原因导致执行顺序变化。有序性的解决方案包括使用volatile关键字、synchronized关键字、建立happens-before关系等。
多线程原子性、一致性与有序性
- 原子性
- 一致性
- 有序性
- volatile
- 案例一
- 案例二
- 案例三
原子性
多线程原子性是指一个操作在多线程环境下执行时,保证这个操作的执行是不可被中断的,即要么全部完成,要么全部不完成
。即使有多个线程同时调用这个操作,也不会造成数据的混乱、错误或不一致性。这种保证是由操作系统提供的,通常使用锁或者原子操作来实现。在多线程编程中,需要特别注意多线程访问共享资源时的原子性问题,以避免出现数据竞争、死锁、饥饿和其他线程安全问题。
一致性
多线程一致性是指在多线程程序中,多个线程之间对共享资源的访问是有序的、正确的、可预测的,并且保证数据的正确性和完整性
。
在多线程程序中,如果多个线程同时访问同一个共享资源,可能会出现访问冲突、竞争条件等问题,导致程序的错误和不一致性。为了保证多线程程序的正确性,需要采取相应的同步机制,如锁、信号量等,来保证每个线程对共享资源的访问是有序的、正确的、可预测的,并且保证数据的正确性和完整性。
同时,要保证多线程程序的正确性,还需要避免“竞态条件”(Race Condition)的出现。竞态条件是指在多线程程序中,多个线程之间对共享资源的访问顺序会发生变化,从而导致程序出现错误和不一致性。为避免竞态条件的出现,可以采用同步机制和原子操作等措施来保证多线程程序的正确性。
有序性
多线程有序性指的是多线程程序的执行顺序不是固定的,可能会出现线程执行顺序与我们期望的不一致
的情况。
在多线程编程中,由于线程的调度和执行是由操作系统决定的,而操作系统会根据一定的算法来决定哪个线程先执行,哪个线程后执行。因此,多线程程序的执行顺序是不确定的,可能会出现不同的执行结果。这种不确定性就是多线程的有序性问题。
多线程有序性问题对程序的正确性会产生影响,可能会导致程序出现异常或者不可预期的结果。为了解决这个问题,我们通常使用同步机制来保证多线程程序的正确性,比如使用互斥锁、条件变量、原子操作等方式来对共享数据进行操作,以保证多线程程序的正确性。
volatile
在多线程并发的情况下,CPU 的指令重排可能会导致代码执行结果与预期不符,这种情况下需要使用 volatile 关键字来保证有序性。
volatile 的作用是禁止编译器或处理器对代码进行指令重排序优化,从而保证代码执行的有序性。也就是说,使用 volatile 声明的变量,每次读取它的值时都会从内存中读取,每次写入它的值时都会立即刷新到内存中。
因此,使用 volatile 关键字可以保证多线程并发访问同一个变量时,能够保证修改的顺序与读取的顺序是一致的,从而避免了数据的不一致和错误的结果。
使用volatile关键字时,编译器与运行时会对它所修饰的变量的访问进行特殊的处理,保证可见性和有序性
volatile并不能保证原子性,如果需要保证变量的操作具有原子性,还需要配合使用synchronized或者Atomic类型
案例一
下面是一个简单的示例,说明了如何使用volatile解决有序性问题:
假设我们有两个线程,一个线程负责修改一个共享变量的值,另一个线程负责读取该共享变量的值。如果我们不使用volatile,那么另一个线程可能会看到共享变量的旧值,因为写入线程的更新可能尚未被刷新到主内存中。这是因为在没有同步的情况下,编译器和处理器可以重新排序操作,以提高性能。但是,这可能导致数据不一致性和意外行为。
下面是使用volatile修饰共享变量的示例代码,以确保写入变量的值立即刷新到主内存,并且所需的happens-before关系已经建立:
class SharedData {volatile int value;
}class WriterThread implements Runnable {private SharedData data;public WriterThread(SharedData data) {this.data = data;}public void run() {// 修改共享变量的值data.value = 42;}
}class ReaderThread implements Runnable {private SharedData data;public ReaderThread(SharedData data) {this.data = data;}public void run() {// 读取共享变量的值System.out.println(data.value);}
}public class Main {public static void main(String[] args) {SharedData data = new SharedData();Thread writerThread = new Thread(new WriterThread(data));Thread readerThread = new Thread(new ReaderThread(data));writerThread.start();readerThread.start();}
}
在上面的示例中,共享变量value被声明为volatile,这意味着写入线程对它的修改会立即刷新到主内存中,而读取线程会从主内存中读取最新的值。因此,读取线程不会看到过期的值,并且对共享变量的访问已建立必要的happens-before关系,以确保正确的顺序。
案例二
以下是一个简单的多线程买票java案例:
public class Ticket implements Runnable {private int ticketCount = 10;public void run() {while (ticketCount > 0) {try {// 模拟网络延迟,增加线程安全性Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (this) { // 使用 synchronized 关键字实现线程同步if (ticketCount > 0) {System.out.println(Thread.currentThread().getName() + "购买了一张票,剩余票数为:" + (--ticketCount));} else {System.out.println(Thread.currentThread().getName() + "发现票已经卖完了");}}}}public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(ticket, "用户1").start();new Thread(ticket, "用户2").start();new Thread(ticket, "用户3").start();}
}
在这个案例中,我们创建了一个 Ticket
类实现了 Runnable
接口,重写了 run()
方法。在 run()
方法中,我们使用 synchronized
关键字来保证同一时刻只有一个线程能够进入临界区(购买票数减 1 的代码块)。这样就保证了不会出现多个线程同时购买同一张票的情况。
在 main()
方法中,我们创建了三个线程启动来购买票,每个线程的名称分别为 “用户1”、“用户2” 和 “用户3”。运行这个程序,我们会看到这三个线程分别购买了 4、3 和 2 张票,最终票被卖完了。
案例三
下面是一个多线程买票的volatile案例:
public class BuyTicket implements Runnable {private volatile int tickets = 10; // 票数private String name; // 线程名字public BuyTicket(String name) {this.name = name;}public void run() {while (tickets > 0) {synchronized (this) {if (tickets > 0) {System.out.println(name + "买到了一张票,剩余" + (--tickets) + "张票。");}}try {Thread.sleep(100); // 暂停100ms} catch (InterruptedException e) {e.printStackTrace();}}}
}
在上面的案例中,tickets
变量是被volatile
修饰的,这表示每个线程都可以读取和修改它的值。同时,用synchronized
关键字来保证线程安全,避免出现多个线程同时读取到同一份票数,产生数据竞争的情况。同时,在每个线程操作之后,都暂停100ms,以让其他线程有机会读取和修改票数,避免出现死循环的情况。
相关文章:
多线程原子性、一致性与有序性
作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言! 前言: …...
读书笔记:Effective C++ 2.0 版,条款28(namespace )
条款28: 划分全局名字空间 namespace 作为前缀,防止不同名字域的类型、常量等互相污染。 没命名的名字空间一般用于限制名字空间内部元素的可见性。 namespace sdm {const double book_version 2.0;class handle { ... };handle& gethandle(); }早期用struct模…...

CSS常见选择器总结
1.简单选择器 简单选择器是开发中使用最多的选择器,包含: 元素选择器,使用元素的名称 类选择器,使用.类名 id选择器,使用#id id注意事项: 一个HTML文档里面的id值 是唯一的,不能重复 id值如…...

阿里云服务结构--长期更新
CNCF 全称Cloud Native Computing Foundation(云原生计算基金会),成立于 2015 年7月21日(于美国波特兰OSCON 2015上宣布),其最初的口号是坚持和整合开源技术来让编排容器作为微服务架构的一部分࿰…...

Java8 BiConsumer<T, U> 函数接口浅析分享(含示例,来戳!)
文章目录 Java8 BiConsumer<T, U> 函数接口浅析分享(含示例,来戳!)源码accept 方法示例示例一示例二 andThen 方法示例示例一示例二 示例相关代码类dohandler 方法student.javaStudentScore.javaStudentScoreDto.java Java8…...
C++学习:类的使用--运算符重载
我们知道C可以对函数进行重载,让同名的函数来完成相同的基本操作。其实运算符也是可以重载的,而且有的运算符已经在使用了,就像*,既可以用于地址,又可以用于乘法。 下面是一个计算时间的类 #ifndef MYTIME_H #define…...

嵌入式养成计划-46----QT--简易版网络聊天室实现--QT如何连接数据库
一百一十九、简易版网络聊天室实现 119.1 QT实现连接TCP协议 119.1.1 基于TCP的通信流程 119.1.2 QT中实现服务器过程 使用QTcpServer实例化一个服务器对象设置监听状态,通过listen()函数,可以监听特定的主机,也可以监听所有客户端&#x…...

YOLO目标检测——人脸识别数据集【对应voc、coco和yolo三种格式标签】
实际项目应用:安全监控、智能驾驶、人机交互、人脸门禁、人脸支付、人脸搜索数据集说明:人脸识别数据集,真实场景的高质量图片数据,数据场景丰富,含有人脸图片标签说明:使用lableimg标注软件标注࿰…...
mybatis日志不打印的问题
在项目中使用了 spring boot,orm 层集成了 mybatis-plus,按照默认配置后发现之前配置的 sql 日志正常,在这里却不正常,鉴于日志使用的是 logback,想到了打印 sql 的日志级别是 debug,所以,按照这…...
【分布式缓存】关于 Memcached 的几个常见问题
关于 Memcached 的几个常见问题 1.Memcached 是怎么工作的?2.Memcached 最大的优势是什么?3.Memcached 和 MySQL 的 querycache 相比,有什么优缺点?4.Memcached 和服务器的 local cache(比如 PHP 的 APC、mmap 文件等&…...

MySQL 三大日志(bin log、redo log、undo log)
redo log redo log (重做日志) 是 InnoDB 存储引擎独有的,它让 MySQL有了崩溃恢复的能力,是事务中实现 持久化的重要操作 比如 MySQL 实例宕机了,重启时,InnoDB 存储引擎会使用 redo log 恢复数据,保证数据的持久性与…...

asp.net社区医疗辅助诊断网站系统VS开发sqlserver数据库web结构c#编程
一、源码特点 asp.net社区医疗辅助诊断网站系统 是一套完善的web设计管理系统,系统采用mvc模式(BLLDALENTITY)系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver200…...

NLP Bi-Encoder和Re-ranker
Retrieve & Re-Rank https://www.sbert.net/examples/applications/retrieve_rerank/README.html Bi-Encoder vs. Cross-Encoder https://www.sbert.net/examples/applications/cross-encoder/README.html Bi-Encoder会用BERT对输入文本编码,再根据cosine相似度…...

SpringBoot+Mybatis 配置多数据源及事务管理
目录 1.多数据源 2.事务配置 项目搭建参考: 从零开始搭建SpringBoot项目_从0搭建springboot项目-CSDN博客 SpringBoot学习笔记(二) 整合redismybatisDubbo-CSDN博客 1.多数据源 添加依赖 <dependencies><dependency><groupId>org.springframework.boot&…...
华为OD 猴子吃桃(100分)【java】A卷+B卷
华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…...

切片不够技术来凑
概述 随着数据经度的提升,18级的切片有些场景已经不够用了,但是大部分在线的栅格切片最大级别还是18级,如果地图继续放大,有的框架(leaflet会,openlayers和mapboxGL不会)会存在没有底图的情况。…...

特约|数码转型思考:Web3.0与银行
日前,欧科云链研究院发布重磅报告,引发银行界及金融监管机构广泛关注。通过拆解全球70余家银行的加密布局,报告认为,随着全球采用率的提升与相关技术的成熟,加密资产已成为银行业不容忽视也不能错过的创新领域。 作为…...
MySQL知识详细汇总
存储引擎 MyISAM 不支持事务,不支持外键,支持全文索引,查询、插入效率高InnoDB 支持事务(事务的特性) 原子性:一个事务中所有的操作,要么全部完成,要么全部不完成,不会在…...

【驱动开发】LED灯的亮灭——通过字符设备驱动的分步实现编写LED驱动,实现设备文件和设备的绑定
头文件: #ifndef __HEAD_H__ #define __HEAD_H__typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t;//LED灯的寄存器地址 #define LED1_ADDR 0X50006000 #define L…...
华为OD 最小数字(100分)【java】A卷+B卷
华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...