电商项目-分布式事务(四)基于消息队列实现分布式事务
基于消息队列实现分布式事务,实现消息最终一致性
如何基于消息队列实现分布式事务?
通过消息队列实现分布式事务的话,可以保证当前数据的最终一致性。实现思路:将大的分布式事务,进行拆分,拆分成若干个小的本地事务,只要每一个小的本地事务是执行成功的,那就代表当前的分布式事务是执行成功的。下面以用户下单增加积分,来进行消息队列实现分布式事务的操作。
功能实现流程图如下:

主要包含三个角色:订单服务,消息队列和用户服务。
步骤(1-4):订单服务
1.首先,在订单服务中,会生成订单相关数据,并添加到数据库中。
2.接着会在订单数据库中,创建任务表 ,会向任务表添加数据,(Username,orderID,point积分)
3.设置定时任务,扫描任务表,获取相关数据,如每隔7S扫描一次
4.发送任务数据到MQ
步骤(5-12):用户服务
5.6.7.用户服务会接收消息,保证消息的不可重复消费,判断在Redis和 在数据库中是否存在。防止重复处理,不要重复添加积分。
用户服务正在执行消息
如果不存在,继续业务功能。
添加本地事务
12.通知订单服务
步骤(13):订单服务
13.删除任务表数据。
一、 准备工作
1.1 shangcheng_order库新增数据表
任务表:
DROP TABLE IF EXISTS `tb_task`;
CREATE TABLE `tb_task` (`id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '任务id',`create_time` datetime DEFAULT NULL,`update_time` datetime DEFAULT NULL,`delete_time` datetime DEFAULT NULL,`task_type` varchar(32) DEFAULT NULL COMMENT '任务类型',`mq_exchange` varchar(64) DEFAULT NULL COMMENT '交换机名称',`mq_routingkey` varchar(64) DEFAULT NULL COMMENT 'routingkey',`request_body` varchar(512) DEFAULT NULL COMMENT '任务请求的内容',`status` varchar(32) DEFAULT NULL COMMENT '任务状态',`errormsg` varchar(512) DEFAULT NULL COMMENT '任务错误信息',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
历史任务表:
DROP TABLE IF EXISTS `tb_task_his`;
CREATE TABLE `tb_task_his` (`id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '任务id',`create_time` datetime DEFAULT NULL,`update_time` datetime DEFAULT NULL,`delete_time` datetime DEFAULT NULL,`task_type` varchar(32) DEFAULT NULL COMMENT '任务类型',`mq_exchange` varchar(64) DEFAULT NULL COMMENT '交换机名称',`mq_routingkey` varchar(64) DEFAULT NULL COMMENT 'routingkey',`request_body` varchar(512) DEFAULT NULL COMMENT '任务请求的内容',`status` varchar(32) DEFAULT NULL COMMENT '任务状态',`errormsg` varchar(512) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
1.2 shangcheng_service_order_api添加相关实体类
@Table(name="tb_task")
public class Task {
@Idprivate Long id;
@Column(name = "create_time")private Date createTime;
@Column(name = "update_time")private Date updateTime;
@Column(name = "delete_time")private Date deleteTime;
@Column(name = "task_type")private String taskType;
@Column(name = "mq_exchange")private String mqExchange;
@Column(name = "mq_routingkey")private String mqRoutingkey;
@Column(name = "request_body")private String requestBody;
@Column(name = "status")private String status;
@Column(name = "errormsg")private String errormsg;//getter,setter略
}
@Table(name="tb_task_his")
public class TaskHis {
@Idprivate Long id;
@Column(name = "create_time")private Date createTime;
@Column(name = "update_time")private Date updateTime;
@Column(name = "delete_time")private Date deleteTime;
@Column(name = "task_type")private String taskType;
@Column(name = "mq_exchange")private String mqExchange;
@Column(name = "mq_routingkey")private String mqRoutingkey;
@Column(name = "request_body")private String requestBody;
@Column(name = "status")private String status;
@Column(name = "errormsg")private String errormsg;
//getter,setter略
}
1.3 shangcheng_user新增积分日志表
DROP TABLE IF EXISTS `tb_point_log`;
CREATE TABLE `tb_point_log` (`order_id` varchar(200) NOT NULL,`user_id` varchar(200) NOT NULL,`point` int(11) NOT NULL,PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1.4 shangcheng_service_user_api添加实体类 PointLog
@Table(name="tb_point_log")
public class PointLog {
private String orderId;private String userId;private Integer point;//getter,setter略
}
1.5 shangcheng_service_order添加rabbitMQ配置类
@Configuration
public class RabbitMQConfig {//添加积分任务交换机public static final String EX_BUYING_ADDPOINTUSER = "ex_buying_addpointuser";
//添加积分消息队列public static final String CG_BUYING_ADDPOINT = "cg_buying_addpoint";
//完成添加积分消息队列public static final String CG_BUYING_FINISHADDPOINT = "cg_buying_finishaddpoint";
//添加积分路由keypublic static final String CG_BUYING_ADDPOINT_KEY = "addpoint";
//完成添加积分路由keypublic static final String CG_BUYING_FINISHADDPOINT_KEY = "finishaddpoint";
/*** 交换机配置* @return the exchange*/@Bean(EX_BUYING_ADDPOINTUSER)public Exchange EX_BUYING_ADDPOINTUSER() {return ExchangeBuilder.directExchange(EX_BUYING_ADDPOINTUSER).durable(true).build();}//声明队列@Bean(CG_BUYING_FINISHADDPOINT)public Queue QUEUE_CG_BUYING_FINISHADDPOINT() {Queue queue = new Queue(CG_BUYING_FINISHADDPOINT);return queue;}//声明队列@Bean(CG_BUYING_ADDPOINT)public Queue QUEUE_CG_BUYING_ADDPOINT() {Queue queue = new Queue(CG_BUYING_ADDPOINT);return queue;}/*** 绑定队列到交换机 .* @param queue the queue* @param exchange the exchange* @return the binding*/@Beanpublic Binding BINDING_QUEUE_FINISHADDPOINT(@Qualifier(CG_BUYING_FINISHADDPOINT) Queue queue, @Qualifier(EX_BUYING_ADDPOINTUSER) Exchange exchange) {return BindingBuilder.bind(queue).to(exchange).with(CG_BUYING_FINISHADDPOINT_KEY).noargs();}@Beanpublic Binding BINDING_QUEUE_ADDPOINT(@Qualifier(CG_BUYING_ADDPOINT) Queue queue, @Qualifier(EX_BUYING_ADDPOINTUSER) Exchange exchange) {return BindingBuilder.bind(queue).to(exchange).with(CG_BUYING_ADDPOINT_KEY).noargs();}
}
二、 订单服务添加任务并发送
步骤1: 修改添加订单方法
当添加订单的时候,添加任务表中相关数据, 代码如下:
//增加任务表记录
Task task = new Task();
task.setCreateTime(new Date());
task.setUpdateTime(new Date());
task.setMqExchange(RabbitMQConfig.EX_BUYING_ADDPOINTURSE);
task.setMqRoutingkey(RabbitMQConfig.CG_BUYING_ADDPOINT_KEY);
Map map = new HashMap();
map.put("userName",order.getUsername());
map.put("orderId",order.getId());
map.put("point",order.getPayMoney());
task.setRequestBody(JSON.toJSONString(map));
taskMapper.insertSelective(task);
步骤2: 定时扫描任务表最新数据
订单服务新增定时任务类,获取小于系统当前时间的所有任务数据
(1) 修改订单服务启动类,添加开启定时任务注解
@EnableScheduling
(2) 定义定时任务类
查询最新数据
更新taskMapper新增方法,查询所有小于系统当前时间的数据
public interface TaskMapper extends Mapper<Task> {
@Select("SELECT * from tb_task WHERE update_time<#{currentTime}")@Results({@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime"),@Result(column = "delete_time",property = "deleteTime"),@Result(column = "task_type",property = "taskType"),@Result(column = "mq_exchange",property = "mqExchange"),@Result(column = "mq_routingkey",property = "mqRoutingkey"),@Result(column = "request_body",property = "requestBody"),@Result(column = "status",property = "status"),@Result(column = "errormsg",property = "errormsg")})List<Task> findTaskLessTanCurrentTime(Date currentTime);
}
任务类实现
@Component
public class QueryPointTask {
@Autowiredprivate RabbitTemplate rabbitTemplate;
@Autowiredprivate TaskMapper taskMapper;
@Scheduled(cron = "0 0/2 * * * ?")public void queryTask(){
//1.获取小于系统当前时间数据List<Task> taskList = taskMapper.findTaskLessTanCurrentTime(new Date());
if (taskList!=null && taskList.size()>0){//将任务数据发送到消息队列for (Task task : taskList) {rabbitTemplate.convertAndSend(RabbitMQConfig.EX_BUYING_ADDPOINTURSE,RabbitMQConfig.CG_BUYING_ADDPOINT_KEY, JSON.toJSONString(task));}}}
}
三、 用户服务更改积分
步骤1: 添加rabbitmq配置类(与订单服务相同)
@Configuration
public class RabbitMQConfig {//添加积分任务交换机public static final String EX_BUYING_ADDPOINTURSE = "ex_buying_addpointurse";
//添加积分消息队列public static final String CG_BUYING_ADDPOINT = "cg_buying_addpoint";
//完成添加积分消息队列public static final String CG_BUYING_FINISHADDPOINT = "cg_buying_finishaddpoint";
//添加积分路由keypublic static final String CG_BUYING_ADDPOINT_KEY = "addpoint";
//完成添加积分路由keypublic static final String CG_BUYING_FINISHADDPOINT_KEY = "finishaddpoint";
/*** 交换机配置* @return the exchange*/@Bean(EX_BUYING_ADDPOINTURSE)public Exchange EX_DECLARE() {return ExchangeBuilder.directExchange(EX_BUYING_ADDPOINTURSE).durable(true).build();}//声明队列@Bean(CG_BUYING_FINISHADDPOINT)public Queue QUEUE_CG_BUYING_FINISHADDPOINT() {Queue queue = new Queue(CG_BUYING_FINISHADDPOINT);return queue;}//声明队列@Bean(CG_BUYING_ADDPOINT)public Queue QUEUE_CG_BUYING_ADDPOINT() {Queue queue = new Queue(CG_BUYING_ADDPOINT);return queue;}/*** 绑定队列到交换机 .* @param queue the queue* @param exchange the exchange* @return the binding*/@Beanpublic Binding BINDING_QUEUE_FINISHADDPOINT(@Qualifier(CG_BUYING_FINISHADDPOINT) Queue queue, @Qualifier(EX_BUYING_ADDPOINTURSE) Exchange exchange) {return BindingBuilder.bind(queue).to(exchange).with(CG_BUYING_FINISHADDPOINT_KEY).noargs();}@Beanpublic Binding BINDING_QUEUE_ADDPOINT(@Qualifier(CG_BUYING_ADDPOINT) Queue queue, @Qualifier(EX_BUYING_ADDPOINTURSE) Exchange exchange) {return BindingBuilder.bind(queue).to(exchange).with(CG_BUYING_ADDPOINT_KEY).noargs();}
}
步骤2: 定义消息监听类:
@Component
public class AddPointListener {
@Autowiredprivate UserService userService;
@Autowiredprivate RedisTemplate redisTemplate;
@Autowiredprivate RabbitTemplate rabbitTemplate;
@RabbitListener(queues = RabbitMQConfig.CG_BUYING_ADDPOINT)public void receiveMessage(String message){
Task task = JSON.parseObject(message, Task.class);
if (task == null || StringUtils.isEmpty(task.getRequestBody())){return;}
//判断redis中是否存在内容Object value = redisTemplate.boundValueOps(task.getId()).get();if (value != null){return;}
//更新用户积分int result = userService.updateUserPoints(task);
if (result<=0){return;}
//返回通知
rabbitTemplate.convertAndSend(RabbitMQConfig.EX_BUYING_ADDPOINTURSE,RabbitMQConfig.CG_BUYING_FINISHADDPOINT_KEY,JSON.toJSONString(task));
}
}
步骤3: 定义修改用户积分实现
实现思路:
1)判断当前订单是否操作过
2)将任务存入redis
3)修改用户积分
4)添加积分日志表记录
5)删除redis中记录
@Autowired
private PointLogMapper pointLogMapper;
/*** 修改用户积分* @param task* @return*/@Override@Transactionalpublic int updateUserPoints(Task task) {Map info = JSON.parseObject(task.getRequestBody(), Map.class);String userName = info.get("userName").toString();String orderId = info.get("orderId").toString();int point = (int) info.get("point");
//判断当前订单是否操作过PointLog pointLog = pointLogMapper.findLogInfoByOrderId(orderId);if (pointLog != null){return 0;}
//将任务存入redisredisTemplate.boundValueOps(task.getId()).set("exist",1,TimeUnit.MINUTES);
//修改用户积分int result = userMapper.updateUserPoint(userName, point);if (result<=0){return result;}
//添加积分日志表记录pointLog = new PointLog();pointLog.setOrderId(orderId);pointLog.setPoint(point);pointLog.setUserId(userName);result = pointLogMapper.insertSelective(pointLog);if (result<=0){return result;}
//删除redis中的记录redisTemplate.delete(task.getId());
return 1;}
步骤4: 定义根据订单id查询积分日志表
定义PointLogMapper,实现根据订单id查询:
public interface PointLogMapper extends Mapper<PointLog> {
@Select("select * from tb_point_log where order_id=#{orderId}")PointLog findLogInfoByOrderId(@Param("orderId") String orderId);
}
四、 订单服务删除原任务
步骤1: 定义监听类
在订单服务中定义监听类,用于监听队列,如果队列中有消息,则删除原任务防止消息重复发送,并对任务信息进行记录
@Component
public class DelTaskListener {
@Autowiredprivate TaskService taskService;
@RabbitListener(queues = RabbitMQConfig.CG_BUYING_FINISHADDPOINT)public void receiveMessage(String message){
Task task = JSON.parseObject(message, Task.class);
taskService.delTask(task);}
}
步骤: 定义任务service
public interface TaskService {
void delTask(Task task);
}
@Service
@Transactional
public class TaskServiceImpl implements TaskService {
@Autowiredprivate TaskMapper taskMapper;
@Autowiredprivate TaskHisMapper taskHisMapper;
@Overridepublic void delTask(Task task) {//1. 设置删除时间task.setDeleteTime(new Date());Long id = task.getId();task.setId(null);
//bean复制TaskHis taskHis = new TaskHis();BeanUtils.copyProperties(task,taskHis);
//记录任务信息taskHisMapper.insertSelective(taskHis);
//删除原任务task.setId(id);taskMapper.deleteByPrimaryKey(task);}
}
相关文章:
电商项目-分布式事务(四)基于消息队列实现分布式事务
基于消息队列实现分布式事务,实现消息最终一致性 如何基于消息队列实现分布式事务? 通过消息队列实现分布式事务的话,可以保证当前数据的最终一致性。实现思路:将大的分布式事务,进行拆分,拆分成若干个小…...
g++ -> make -> cmake(草稿)
1 Windows上安装mingw 2 构建一个 c 项目 3 g 编译 4 make 编译 5 cmake 编译...
JSON常用的工具方法
前言: 在日常开发中,JSON 数据的处理是常见的需求。无论是数据转换、格式化还是与其他格式的互转,掌握一些常用的工具方法可以大大提高开发效率。本文将介绍一些实用的 JSON 操作方法,帮助你快速上手。 JSON常用的工具方法 1.json字符串转换…...
【Kubernetes Pod间通信-第2篇】使用BGP实现Pod到Pod的通信
Kubernetes中Pod间的通信 本系列文章共3篇: 【Kubernetes Pod间通信-第1篇】在单个子网中使用underlay网络实现Pod到Pod的通信【Kubernetes Pod间通信-第2篇】使用BGP实现Pod到Pod的通信(本文介绍)【Kubernetes Pod间通信-第3篇】Kubernetes中Pod与ClusterIP服务之间的通信…...
[权限提升] Windows 提权 维持 — 系统错误配置提权 - Trusted Service Paths 提权
关注这个专栏的其他相关笔记:[内网安全] 内网渗透 - 学习手册-CSDN博客 0x01:Trusted Service Paths 提权原理 Windows 的服务通常都是以 System 权限运行的,所以系统在解析服务的可执行文件路径中的空格的时候也会以 System 权限进行解析&a…...
8. k8s二进制集群之Kubectl部署
创建kubectl证书请求文件生成admin证书文件复制admin证书到指定目录生成kubeconfig配置文件接下来完成kubectl配置文件的角色绑定【扩展】kubectl命令补全操作继续上一篇文章《k8s二进制集群之Kube ApiServer部署》下面介绍一下k8s中的命令行管理工具kubectl。 通过kubectl可以…...
初学 Xvisor 之理解并跑通 Demo
官网:https://www.xhypervisor.org/ quick-start 文档:https://github.com/xvisor/xvisor/blob/master/docs/riscv/riscv64-qemu.txt 零、Xvisor 介绍 下面这部分是 Xvisor 官方的介绍 Xvisor 是一款开源的 Type-1 虚拟机管理程序,旨在提供一…...
深度内容运营与开源AI智能名片2+1链动模式S2B2C商城小程序在打造种草社区中的应用研究
摘要:移动互联网的迅猛发展极大地改变了消费者的购物行为和消费习惯,传统的购物体验已难以满足用户日益增长的个性化需求。在这种背景下,深度内容运营和实时互动成为提升用户购物体验、影响用户购物行为的重要手段。同时,开源AI智…...
RNN/LSTM/GRU 学习笔记
文章目录 RNN/LSTM/GRU一、RNN1、为何引入RNN?2、RNN的基本结构3、各种形式的RNN及其应用4、RNN的缺陷5、如何应对RNN的缺陷?6、BPTT和BP的区别 二、LSTM1、LSTM 简介2、LSTM如何缓解梯度消失与梯度爆炸? 三、GRU四、参考文献 RNN/LSTM/GRU …...
音频录制一般在什么情况下会选择保存为PCM?什么情况会选择保存为WAV?
在音频开发中,选择保存为 PCM 或 WAV 格式取决于具体的应用场景和需求。以下是两种格式的特点以及适用场景的分析: PCM 格式 特点: 原始音频数据: PCM 是未压缩的原始音频数据,没有任何文件头或元数据。数据直接以二进…...
C#常用744单词
1.visual 可见的 2.studio 工作室 3.dot 点 4.net 网 5.harp 尖端的,锋利的。 6.amework 骨架,构架,框架 7.beta 测试版,试用版 8.XML(全称:eXtensible Markup Language)…...
如何理解算法的正确性?
循环不变式(Loop Invariant) 是算法设计和程序验证中的一个核心概念,用于证明循环的正确性。它是在循环的每次迭代开始和结束时均保持为真的一种条件或性质,帮助开发者确保循环按预期工作,最终达到目标状态。 循环不变…...
蓝桥杯试题:排序
一、问题描述 给定 nn 个正整数 a1,a2,…,ana1,a2,…,an,你可以将它们任意排序。现要将这 nn 个数字连接成一排,即令相邻数字收尾相接,组成一个数。问,这个数最大可以是多少。 输入格式 第一行输入一个正整数 nnÿ…...
实验十一 Servlet(二)
实验十一 Servlet(二) 【实验目的】 1.了解Servlet运行原理 2.掌握Servlet实现方式 【实验内容】 改造实验10,引入数据库,创建用户表,包括用户名和密码:客户端通过login.jsp发出登录请求,请求…...
第五天 初步了解ArkTS和ArkUI
初步了解ArkTS和ArkUI,可以从以下几个方面进行概述: 一、ArkTS简介 定义与关系: ArkTS是HarmonyOS(鸿蒙系统)优选的主力应用开发语言。它基于TypeScript(TS)进行扩展,兼容TS的所有特…...
java中的锁面试题
1、多线程中 synchronized 锁升级的原理是什么? synchronized 是JVM层面的锁,是 Java 关键字,通过 monitor 对象来完成,synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、重量级锁 synchronized 锁升级…...
ES6 变量解构赋值总结
1. 数组的解构赋值 1.1 基本用法 // 基本数组解构 const [a, b, c] [1, 2, 3]; console.log(a); // 1 console.log(b); // 2 console.log(c); // 3// 跳过某些值 const [x, , y] [1, 2, 3]; console.log(x); // 1 console.log(y); // 3// 解构剩余元素 const [first, ...re…...
知识蒸馏教程 Knowledge Distillation Tutorial
来自于:Knowledge Distillation Tutorial 将大模型蒸馏为小模型,可以节省计算资源,加快推理过程,更高效的运行。 使用CIFAR-10数据集 import torch import torch.nn as nn import torch.optim as optim import torchvision.tran…...
DeepSeek各版本说明与优缺点分析
DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列,其在不同版本的发布过程中,逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本,从版本的发布时间、特点、优势以及不足之处࿰…...
java进阶专栏的学习指南
学习指南 java类和对象java内部类和常用类javaIO流 java类和对象 类和对象 java内部类和常用类 java内部类精讲Object类包装类的认识String类、BigDecimal类初探Date类、Calendar类、SimpleDateFormat类的认识java Random类、File类、System类初识 javaIO流 java IO流【…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...
【多线程初阶】单例模式 指令重排序问题
文章目录 1.单例模式1)饿汉模式2)懒汉模式①.单线程版本②.多线程版本 2.分析单例模式里的线程安全问题1)饿汉模式2)懒汉模式懒汉模式是如何出现线程安全问题的 3.解决问题进一步优化加锁导致的执行效率优化预防内存可见性问题 4.解决指令重排序问题 1.单例模式 单例模式确保某…...
自动化立体仓库堆垛机控制系统STEP7 OB1功能块
1、堆垛机控制系统STEP7硬件组态如下图 CPU CPU 314C-2 PN/DP 6ES7 314-6EH04-0AB0 SM 338 POS-INPUT AO2x12Bit 6ES7 332-5HB01-0AB0 2、堆垛机控制系统STEP7内部变量 前进HMI M 0.0 BOOL 后退HMI M 0.1 BOOL 上升HMI M 0.2 B…...
