秒杀案例-分布式锁Redisson、synchronized、RedLock

模拟秒杀

  • 源码地址
  • 前期准备
    • 创建数据库表
    • 导入数据
    • dependencies
    • pom
    • Controller
    • TSeckillProduct
    • TseckillProductService
    • TseckillProductServiceImpl
    • TseckillProductMapper
    • TseckillProductMapper.xml
    • 使用JMeter压力测试
    • 开始测试
    • 超卖现象
    • 原因
    • 解决办法
    • 更改数据库库存500
    • 进行JMeter压力测试
    • 还有一种办法 在扣库存的时候加锁再去查询一下库存的数量
    • sql语句恢复原来的
  • 分布式锁
  • 整合Redisson分布式锁
    • 添加依赖
    • 添加配置文件
    • 添加redisson.yml
    • 更改扣库存代码TseckillProductServiceImpl.updateStockCount
    • 更改updateStockCount
    • 吞吐量
    • RedLock
    • 配置类
    • 案例代码
    • 异步Servlet
      • 传统Servlet请求示意图
      • 异步Servlet请求示意图
    • 测试异步Servlet
  • 库存预热
    • increment()
    • putIfAbsent 初始化库存
    • 代码初始化库存和redis递减秒杀
      • 初始化库存 Controller
      • TseckillProductServiceImpl
    • 设置初始化库存
    • 压力测试访问秒杀 /sec/preheat
    • 结果 发现库存没有多扣
    • 同步吞吐量
  • 使用异步线程池的方式进行秒杀
    • 代码
      • Controller
      • TseckillProductServiceImpl
    • 异步吞吐量
    • 使用redis的increment 方法实现递增递减的时候还需要加锁吗
  • 吞吐量分析
  • 优化线程池配置
    • 异步处理吞吐量成功提高

源码地址

https://gitee.com/Lin-seven/seckill

前期准备

创建数据库表

CREATE TABLE `t_seckill_product` (`id` bigint NOT NULL AUTO_INCREMENT,`product_id` bigint DEFAULT NULL,`seckill_price` decimal(10,2) DEFAULT NULL,`intergral` decimal(10,0) DEFAULT NULL,`stock_count` int DEFAULT NULL,`start_date` date DEFAULT NULL,`time` int DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb3;

导入数据

INSERT INTO `shop-seckill`.`t_seckill_product`(`id`, `product_id`, `seckill_price`, `intergral`, `stock_count`, `start_date`, `time`) VALUES (2, 23, 3699.00, 36990, 10, '2024-07-15', 10);

dependencies

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.79</version></dependency></dependencies>

pom

# 应用服务 WEB 访问端口
server:port: 8899
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/shop-seckill?serverTimezone=GMT%2B8username: rootpassword: 123456application:name: shop-seckill#开启日志
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath*:/**/mapper/**/*.xml

Controller

package com.lx.seckill.controller;import com.lx.seckill.pojo.TSeckillProduct;
import com.lx.seckill.service.TseckillProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.UUID;/*** TODO 添加描述** @author wangLJ* @date 2024/7/18 11:33*/
@RestController
@RequestMapping("/sec")
public class SeckillContoller {public Integer count =0;@Autowiredprivate TseckillProductService service;@GetMapping("/list")public List<TSeckillProduct> getList(){List<TSeckillProduct> list = service.list();System.out.println(count);return list;}@GetMapping("/byid")public  TSeckillProduct  byid(){TSeckillProduct  list = service.byid();return list;}@GetMapping("/kill")public String kill()  {TSeckillProduct  list = service.byid();if (list.getStockCount()>0){//扣减库存service.updateStockCount();}return "ok";}}

TSeckillProduct

@Data
//@TableName(value = "eric_user")
public class TSeckillProduct {@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 商品ID*/private Integer productId;// 价格private Integer seckillPrice;//价格private Integer intergral;//数量private Integer stockCount;//场次private Integer time;}

TseckillProductService

public interface TseckillProductService {List<TSeckillProduct> list();TSeckillProduct byid();void updateStockCount();
}

TseckillProductServiceImpl

@Service
public class TseckillProductServiceImpl extends ServiceImpl<TseckillProductMapper, TSeckillProduct> implements TseckillProductService{@AutowiredTseckillProductMapper mapper;@Overridepublic List<TSeckillProduct> list() {return super.list();}@Overridepublic TSeckillProduct byid() {TSeckillProduct tSeckillProduct = mapper.selectById(2);return tSeckillProduct;}@Overridepublic  void updateStockCount() {//扣减库存mapper.updateStockCount( );}}

TseckillProductMapper

@Mapper
public interface  TseckillProductMapper extends BaseMapper<TSeckillProduct> {void updateStockCount();
}

TseckillProductMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.lx.seckill.mapper.TseckillProductMapper"><update id="updateStockCount">UPDATE  t_seckill_product set stock_count=stock_count-1 where id =2</update>
</mapper>

使用JMeter压力测试

安装教程https://blog.csdn.net/m0_37583655/article/details/126507267-靖节先生
线程组
在这里插入图片描述

安装好之后添加查看结果树和聚合报告
在这里插入图片描述

开始测试

在这里插入图片描述
填好对应的后端地址 点击开始

超卖现象

在这里插入图片描述
发现库存都成负数了。这是为什么呢?

原因

原因在于,有很多线程同时执行了service.byid();方法
拿到的库存,保存在自己的栈帧中。如果有一百个线程拿到的库存是大于0的,就有一百个线程去执行扣库存的方法

解决办法

使用synchronized
在去查询库存的时候加锁

  @Overridepublic  synchronized TSeckillProduct byid() {TSeckillProduct tSeckillProduct = mapper.selectById(2);return tSeckillProduct;}

更改数据库库存500

在这里插入图片描述

进行JMeter压力测试

在这里插入图片描述
通过测试发现,库存总是等于-1
这是因为
**读取与更新之间的窗口期:**当一个线程读取库存后,其他线程可能已经完成了库存的扣减操作,但当前线程由于持有库存值的本地副本,仍可能认为库存充足而继续执行扣减操作。
数据库事务隔离级别:即使使用了@Transactional注解,如果数据库的事务隔离级别设置不当(例如,默认的READ_COMMITTED),在读取库存和更新库存之间,其他事务可能已经改变了库存值,导致当前事务基于过期数据进行操作。

为了防止库存值变为负数,您可以采取以下几种策略之一:
**使用乐观锁:**在数据库表中增加一个版本号字段,每次更新库存时也更新版本号,并在更新语句中加入版本号的检查,以确保数据的一致性。

 UPDATE t_seckill_product SET stock_count = stock_count - 1 WHERE id = 2 AND stock_count > 0

**使用悲观锁:**在查询库存时即锁定库存记录,直到事务结束。这可以通过SQL语句中的FOR UPDATE关键字实现。
原子操作:使用数据库提供的原子操作,如MySQL的INNODB存储引擎支持的原子自减操作,直接在数据库层面完成库存的扣减和检查。

UPDATE t_seckill_product SET stock_count = stock_count - 1 WHERE id = 2   FOR UPDATE

还有一种办法 在扣库存的时候加锁再去查询一下库存的数量

去除TSeckillProduct方法的synchronized 在updateStockCount方法加上synchronized

    @Overridepublic    TSeckillProduct byid() {TSeckillProduct tSeckillProduct = mapper.selectById(2);return tSeckillProduct;}@Transactional@Overridepublic  synchronized void updateStockCount() {TSeckillProduct tSeckillProduct = mapper.selectById(2);if (tSeckillProduct.getStockCount()>0){//扣减库存mapper.updateStockCount( );}}

sql语句恢复原来的

 <update id="updateStockCount">UPDATE  t_seckill_product set stock_count=stock_count-1 where id =2</update>

分布式锁

如果是集群架构,以上使用synchronized 就不适用了。因为synchronized 锁住的只是当前JVM中的this对象,集群状态下,还是有可能发生并发关系,同时执行到updateStockCount,当获取的库存都会存入自己的栈帧,所以还会导致库存超卖的情况

整合Redisson分布式锁

添加依赖

<!--        redisson分布式锁--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.13.6</version></dependency>

添加配置文件

  # redis配置
spring:redis:database: 0host: 127.0.0.1
#    password: redis@passport: 6379redisson:file: classpath:redisson.yml

添加redisson.yml

application.yml与 redisson.yml在同级,目录结构如下:

# 单节点配置
singleServerConfig:# 数据库编号database: 0# 节点地址address: redis://127.0.0.1:6379# 密码
#  password: redis@pass

更改扣库存代码TseckillProductServiceImpl.updateStockCount

注入RedissonClient

 @ResourceRedissonClient redissonClient;

更改updateStockCount

@Transactional@Overridepublic    void updateStockCount() {/*** 获取一把锁,只要锁的名字一样,就是同一把锁,"my-lock"是锁名,也是Redis的哈希模型的对外key*/RLock lock = redissonClient.getLock("my-lock");//加锁/*** 阻塞式等待,默认加的锁等待时间为30s。每到20s(经过三分之一看门狗时间后)就会自动续借成30s* 1.锁的自动续期,如果在业务执行期间业务没有执行完成,redisson会为该锁自动续期* 2.加锁的业务只要运行完成,就不会自动续期,即使不手动解锁,锁在默认的30s后会自动删除*/// lock.lock();/*** (推荐)指定锁的过期时间,看门狗不会自动续期:* 在自定义锁的存在时间时不会自动解锁* 注意:* 设置的自动解锁时间一定要稳稳地大于业务时间*/lock.lock(30, TimeUnit.SECONDS);try {TSeckillProduct tSeckillProduct = mapper.selectById(2);if (tSeckillProduct.getStockCount()>0){//扣减库存mapper.updateStockCount( );}} finally {//释放锁lock.unlock();}}

分布式公平锁、读写锁、信号量、闭锁请查看文档
https://www.yuque.com/manmushanhe-kfrkq/pgm4gc/lpmvfoqmo1yzq8yv

吞吐量

在这里插入图片描述

RedLock

分布式锁算法,通过多个Redis实例来实现高可用的分布式锁服务,确保分布式系统中的数据安全和一致性。
适用于redis 集群模式下

依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.2</version>
</dependency>

配置类

@Configuration
public class RedLockConfig {@Bean(destroyMethod="shutdown")RedissonClient redissonClient() {Config config = new Config();// 可以配置多个Redis节点config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001");return Redisson.create(config);}
}

案例代码

  @Autowiredprivate RedissonClient redissonClient;public void yourMethod() {// 获取RedLockRedLock redLock = new RedLock(redissonClient);// 加锁boolean lockAcquired = redLock.lock("yourLockKey");try {if (lockAcquired) {// 业务逻辑} else {// 处理锁竞争}} finally {// 释放锁redLock.unlock("yourLockKey");}}

异步Servlet

传统Servlet请求示意图

在这里插入图片描述

同步阻塞: 传统Servlet采用同步阻塞的方式处理请求。每个请求到达时,Servlet容器会为其分配一个线程,直到请求处理完成超时才释放线程
线程资源消耗: 每个请求都需要一个独立的线程,当请求需要等待IO操作或其他资源时,线程会被阻塞,导致资源浪费。
**简单易用:**开发和理解成本较低,符合传统的Servlet编程模型,适用于一般性的Web应用。

适用场景:

小规模并发请求的应用场景。
对响应时间和吞吐量要求不高的应用。

异步Servlet请求示意图

在这里插入图片描述

非阻塞异步:异步Servlet通过异步处理请求,不会阻塞服务器的主线程,可以在处理请求的过程中释放线程资源,等待IO操作完成后再继续处理。
性能优化:能够在高并发情况下显著提高系统的吞吐量和响应速度,有效地利用系统资源。

适用场景:
高并发请求的应用场景,特别是需要处理大量IO操作的情况,如文件上传、推送服务等。
对于需要提高系统性能和响应速度的要求较高的应用。

传统Servlet和异步Servlet虽然都不能提高用户的等待时间但是能够提高系统的线程的利用率

作用:异步Servlet允许Servlet容器在处理请求时不阻塞线程,从而提高服务器的并发处理能力和资源利用率。它特别适合处理需要长时间计算或等待外部资源响应的请求,如文件上传、图像处理等。
实现方式:通过AsyncContext接口实现,允许Servlet在处理请求时将其放入异步处理模式,然后在后台线程完成处理,并在处理完成后返回响应。

测试异步Servlet

创建线程池

@Configuration
public class AsyncConfig {@Bean(name = "threadPoolTaskExecutor")public ThreadPoolTaskExecutor threadPoolTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5); // 核心线程数executor.setMaxPoolSize(10); // 最大线程数executor.setQueueCapacity(20); // 队列大小executor.setThreadNamePrefix("MyExecutor-"); // 线程名前缀executor.initialize();return executor;}}

**测试 使用CompletableFuture开启异步线程 **

 @Autowiredprivate ThreadPoolTaskExecutor executor;/***  异步请求* @return*/@GetMapping("/asynd")@Async("threadPoolTaskExecutor") // 指定使用哪个TaskExecutorpublic CompletableFuture<String> handleAsyncRequest() {return CompletableFuture.supplyAsync(() -> {String threadName = Thread.currentThread().getName();System.out.println("阿里 - " +threadName);// 模拟一个耗时操作try {Thread.sleep(5000);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("Sleep interrupted", e);}return "Hello from async method!";}, executor);}

库存预热

库存每次都去查询,这样会造成每次连接数据库,就会造成数据库的压力。
可以使用redisdincrement 方法 实现递增的功能

increment()

increment() 方法是原子性的,确保多线程或多个客户端同时对同一个字段进行递增操作时的线程安全性 如果Redis中不存在这个key
的值, 会将其初始化为0然后执行递增操作。

putIfAbsent 初始化库存

opsForHash().putIfAbsent() 方法的作用是在哈希表中设置字段的值,
仅当字段不存在时才会设置成功。如果字段已经存在,这个方法不会更新现有的值,而是保持当前的值不变。
如果想要改变key的值 可以直接使用PUT 方法

代码初始化库存和redis递减秒杀

初始化库存 Controller

  /*** 初始化库存* @return*/@RequestMapping("/putIfAbsent")@ResponseBodypublic String  putIfAbsent() {return service.putIfAbsent();}/*** 库存预热-秒杀* @return*/@RequestMapping("/preheat")@ResponseBodypublic String  preheat() {return service.preheat();}

TseckillProductServiceImpl

 @Autowiredprivate StringRedisTemplate redisTemplate;@Overridepublic String putIfAbsent() {/*** opsForHash().putIfAbsent() 方法的作用是在哈希表中设置字段的值,* 但仅当字段不存在时才会设置成功。如果字段已经存在,这个方法不会更新现有的值,而是保持当前的值不变。*/TSeckillProduct tSeckillProduct = mapper.selectById(2);Integer stockCount = tSeckillProduct.getStockCount();/*** 应该使用putIfAbsent 方法* redisTemplate.opsForHash().putIfAbsent("product:stock", "quantity", stockCount+"");* 也可以使用*  redisTemplate.opsForValue().setIfAbsent("product:stock", stockCount + "");* 测试为了方便设置库存,从而采用put 方法*///redisTemplate.opsForHash().putIfAbsent("product:stock", "quantity", stockCount+"");redisTemplate.opsForHash().put("product:stock", "quantity", stockCount+"");return "库存"+stockCount ;}@Overridepublic String preheat() {Long result = redisTemplate.opsForHash().increment("product:stock", "quantity", -1);System.out.println(result);if (result >= 0) {//扣减库存mapper.updateStockCount();return result + "";} else {return "库存不足";}}

设置初始化库存

初始化库存----- http://localhost:8899/sec/putIfAbsent

压力测试访问秒杀 /sec/preheat

在这里插入图片描述

结果 发现库存没有多扣

同步吞吐量

在这里插入图片描述

使用异步线程池的方式进行秒杀

代码

Controller

 @RequestMapping("/asynPreheat")@ResponseBodypublic CompletableFuture<String>  asynPreheat() {return service.asynPreheat();}

TseckillProductServiceImpl

  @Async("threadPoolTaskExecutor") // 指定使用哪个TaskExecutor@Overridepublic CompletableFuture<String> asynPreheat() {return CompletableFuture.supplyAsync(() -> {Long result = redisTemplate.opsForHash().increment("product:stock", "quantity", -1);// 模拟一个耗时操作if (result >= 0) {//扣减库存mapper.updateStockCount();return result + "";} else {return "库存不足";}}, executor);}}

异步吞吐量

在这里插入图片描述

使用redis的increment 方法实现递增递减的时候还需要加锁吗

是 Redis 提供的原子性操作,确保在并发情况下递增或递减哈希表中的字段值是线程安全的。这是因为 Redis 单个命令的执行是原子性的,不会被其他命令打断,所以递增或递减操作是安全的。

吞吐量分析

为什么使用线程池的asynPreheat的吞吐量是10.5/sec(低) 反而没有使用线程池preheat 的吞吐量是919/sec(较高)

asynPreheat() 方法:

使用了 @Async 注解,表明该方法会在一个单独的线程中执行,并且指定了一个自定义的线程池 (executor)。
异步方法通常会有额外的线程切换和调度开销,尤其是在高并发情况下,线程池可能会面临任务排队和等待执行的情况。

preheat() 方法:

这是一个同步方法,每次调用都会在当前线程中执行,没有额外的线程开销。 吞吐量高达 919/sec
可能是因为每次请求都能快速响应,并且没有异步等待线程切换的开销

优化线程池配置

  1. 更改核心线程数
  2. 更改最大线程数
  3. 更改队列大小
@Bean(name = "threadPoolTaskExecutor")public ThreadPoolTaskExecutor threadPoolTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(20); // 核心线程数executor.setMaxPoolSize(30); // 最大线程数executor.setQueueCapacity(90); // 队列大小executor.setThreadNamePrefix("MyExecutor-"); // 线程名前缀executor.initialize();return executor;}

异步处理吞吐量成功提高

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运维锅总详解Kubernetes之Kubelet

本文尝试从Kubelet的发展历史、实现原理、交互逻辑、伪代码实现及最佳实践5个方面对Kubelet进行详细阐述。希望对您有所帮助&#xff01; 一、kubelet发展历史 Kubelet 是 Kubernetes 中的核心组件之一&#xff0c;负责管理单个节点上的容器运行。它的发展历史和功能演进是 Kub…

【LeetCode】222. 完全二叉树的个数

什么是计算机基础&#xff1f;如果本题能够用二分二进制二叉树的方式解出本题&#xff0c;那么我可以认为你的计算机基础就很好了。很久以来&#xff0c;我一直认为自己的计算机基础好&#xff0c;但是自刷题以来&#xff0c;跟网上这么多优秀的同学相比&#xff0c;我发现我实…

五分钟学会 Docker Registry 搭建私有镜像仓库

在上一篇文章《前端不懂 Docker &#xff1f;先用它换掉常规的 Vue 项目部署方式》中&#xff0c;我们学习了如何使用 aliyun 私有镜像仓库&#xff0c;也了解到可以使用 Docker Registry 搭建私有镜像仓库。这篇文章就分享下实操过程。 registry 是官方提供的 registry 镜像&…

WEB前端09-前端服务器搭建(Node.js/nvm/npm)

前端服务器的搭建 在本文中&#xff0c;我们将介绍如何安装和配置 nvm&#xff08;Node Version Manager&#xff09;以方便切换不同版本的 Node.js&#xff0c;以及如何设置 npm&#xff08;Node Package Manager&#xff09;使用国内镜像&#xff0c;并搭建一个简单的前端服…

类和对象(三)

默认成员函数 接下来继续看剩下的两个默认成员函数。 const成员函数 将const修饰的成员函数称之为const成员函数&#xff0c;const修饰成员函数放到成员函数参数列表的后 ⾯。const实际修饰该成员函数隐含的this指针&#xff0c;表明在该成员函数中不能对类的任何成员进⾏修…

秋招突击——7/17——复习{二分查找——搜索插入位置、搜索二维矩阵,}——新作{链表——反转链表和回文链表,子串——和为K的子数组}

文章目录 引言新作二分模板二分查找——搜索插入位置复习实现 搜索二维矩阵复习实现 新作反转链表个人实现参考实现 回文链表个人实现参考实现 和为K的子数组个人实现参考实现 总结 引言 今天算法得是速通的&#xff0c;严格把控好时间&#xff0c;后面要准备去面试提前批了&a…

C语言实例-约瑟夫生者死者小游戏

问题&#xff1a; 30个人在一条船上&#xff0c;超载&#xff0c;需要15人下船。于是人们排成一队&#xff0c;排队的位置即为他们的编号。报数&#xff0c;从1开始&#xff0c;数到9的人下船&#xff0c;如此循环&#xff0c;直到船上仅剩15人为止&#xff0c;问都有哪些编号…

C语言 | Leetcode C语言题解之第260题只出现一次的数字III

题目&#xff1a; 题解&#xff1a; int* singleNumber(int* nums, int numsSize, int* returnSize) {int xorsum 0;for (int i 0; i < numsSize; i) {xorsum ^ nums[i];}// 防止溢出int lsb (xorsum INT_MIN ? xorsum : xorsum & (-xorsum));int type1 0, type2…

【Mysql】Docker下Mysql8数据备份与恢复

[TOC] 【Mysql】Docker下Mysql8数据备份与恢复 1 创建Mysql容器 格式 docker run -d --name容器名称 -p 宿主端口号:3306 -e MYSQL_ROOT_PASSWORDmysql密码 -e MYSQL_PASSWORDmysql密码 -e TZAsia/Shanghai -v 宿主目录-数据:/var/lib/mysql -v 宿主目录-备份数据:/back…

多态性概念 OOPS

大家好&#xff01;今天&#xff0c;我们将探讨面向对象编程 (OOP) 中的一个基本概念 - 多态性。具体来说&#xff0c;我们将重点介绍其三种主要形式&#xff1a;方法重载、方法覆盖和方法隐藏。对于任何使用 OOP 语言&#xff08;例如 C#&#xff09;的程序员来说&#xff0c;…

NET 语言识别,语音控制操作、语音播报

System.Speech. 》》System.Speech.Synthesis; 语音播报 》》System.Speech.Recognition 语音识别 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Speech.Recog…

mac二进制安装operator-sdk

0. 前置条件 1. 安装go 安装步骤略。 1. 下载operator-sdk源码包 https://github.com/operator-framework/operator-sdk 1.1 选择适合当前go版本的operator版本&#xff0c;在operator-sdk/go.mod文件中可以查看Operator-sdk使用的go版本。 2. 编译 源码包下载后&#x…

C语言航空售票系统

以下是系统部分页面 以下是部分源码&#xff0c;需要源码的私信 #include<stdio.h> #include<stdlib.h> #include<string.h> #define max_user 100 typedef struct ft {char name[50];//名字char start_place[50];//出发地char end_place[50];//目的地char …

JAVA 异步编程(线程安全)二

1、线程安全 线程安全是指你的代码所在的进程中有多个线程同时运行&#xff0c;而这些线程可能会同时运行这段代码&#xff0c;如果每次运行的代码结果和单线程运行的结果是一样的&#xff0c;且其他变量的值和预期的也是一样的&#xff0c;那么就是线程安全的。 一个类或者程序…

多线程初阶(二)- 线程安全问题

目录 1.观察count 原因总结 2.解决方案-synchronized关键字 &#xff08;1&#xff09;synchronized的特性 &#xff08;2&#xff09;如何正确使用 语法格式 3.死锁 &#xff08;1&#xff09;造成死锁的情况 &#xff08;2&#xff09;死锁的四个必要条件 4.Java标准…

若依二次开发

口味改造 原&#xff1a; 改造&#xff1a; 1./** 定义口味名称和口味列表的静态数据 */ 2.改变页面样式 3.定义储存当前选中的口味列表数组&#xff0c;定义改变口味名称时更新当前的口味列表 4.改变页面样式 6.格式转换 7.定义口味列表获取焦点时更新当前选中的口味列表

【DGL系列】简单理解graph.update_all和spmm的区别

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 背景介绍 源码分析 小结一下 背景介绍 我们在看GNN相关的论文时候&#xff0c;都会说到邻接矩阵与特征矩阵之间是用到了spmm&#xff0c;在很久…

深入理解Linux网络(二):UDP接收内核探究

深入理解Linux网络&#xff08;二&#xff09;&#xff1a;UDP接收内核探究 一、UDP 协议处理二、recvfrom 系统调⽤实现 一、UDP 协议处理 udp 协议的处理函数是 udp_rcv。 //file: net/ipv4/udp.c int udp_rcv(struct sk_buff *skb) {return __udp4_lib_rcv(skb, &udp_…

【web】-反序列化-to_string

<?php highlight_file(__FILE__); class A{public $s;public function __destruct(){echo "hello".$this->s;}} class B{public $cmd;public function __toString(){system($this->cmd);return 1;} } unserialize($_GET[code]); __toString()当对象被当着…

《梦醒蝶飞:释放Excel函数与公式的力量》17.1使用命名范围和工作表函数

第17章&#xff1a;使用命名范围和工作表函数 17.1 命名范围的优势 在Excel中&#xff0c;使用命名范围是一个强大且灵活的功能&#xff0c;它可以极大地提高工作效率和公式的可读性。命名范围不仅使公式更容易理解&#xff0c;还减少了错误的可能性。以下将详细介绍命名范围的…