以抢红包为例带你走进高并发编程

本文通过模拟5000人抢3000个红包的场景,探讨高并发编程中遇到的超发现象及其解决方案。文章详细介绍了使用悲观锁、乐观锁以及Redis+Lua来解决并发问题的原理和步骤,分析了各种策略的优缺点,为处理并发问题提供了实际操作示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.写在前面

2.模拟超发现象

2.1 概述

2.2 数据库建表

2.3 编写SQL

2.4 编写Mapper接口

2.5 编写Service

2.6 编写Controller

2.7 模拟用户请求

2.8 测试超发现象

3.解决并发之-悲观锁

3.1 概述

3.2 改写Sql

3.3 测试悲观锁

3.4 悲观锁存在的问题

4.解决并发之-乐观锁

4.1 概述

4.2 改写sql

4.3 乐观锁重入机制

4.3.1 概述

4.3.2 代码实现

4.4 测试乐观锁

5.解决并发之-Redis+Lua

6.总结 


1.写在前面


本案例模拟的是5000个人同时抢红包池中的3000个红包的场景,以此案例来讨论高并发编程会遇到的问题以及如何解决。

本案例在全注解SSM环境中编写,关于如何搭建SSM,可以参考博主其他博客:

《全注解搭建SSM》https://blog.youkuaiyun.com/czr11616/article/details/84325586


2.模拟超发现象


2.1 概述

什么是超发现象?

在本案例下,红包池中一共只有3000个红包,5000个人同时抢,最后会出现有超过3000个人抢到了红包,红包池中的剩余红包数变成了负数,这就是超发现象

超发现象产生的原因?

先来看看抢红包的逻辑,在抢红包之前,需要判断红包池中是否还有红包,如果有,则红包池中的剩余红包数减1,并新增一条抢到红包的记录,如果没有,直接返回抢红包失败。

在这样的逻辑下,假设最后红包池中只有一个红包了,有5个人在同一时刻去抢,这时他们会同时判断红包池中是否还有红包,发现有,红包池中剩余红包数减一并新增抢红包记录。最后就会出现有3004个人抢到了红包,红包池中的红包数变成了负4,超发现象就这样产生了。

综上,超发现象产生的根本原因就是由于多线程操作同一条数据从而导致数据不一致。。

接下来模拟超发现象

2.2 数据库建表

创建红包池表 t_red_packet ,并插入红包数据,下面是建表和插入数据sql语句

-- ----------------------------
-- Table structure for t_red_packet
-- ----------------------------
DROP TABLE IF EXISTS `t_red_packet`;
CREATE TABLE `t_red_packet` (
  `id` int(12) NOT NULL AUTO_INCREMENT,
  `user_id` int(12) NOT NULL,
  `amount` decimal(16,2) NOT NULL,
  `send_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `total` int(12) NOT NULL,
  `unit_amount` decimal(12,0) NOT NULL,
  `stock` int(12) NOT NULL,
  `version` int(12) NOT NULL DEFAULT '0',
  `note` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_red_packet
-- ----------------------------
INSERT INTO `t_red_packet` VALUES ('1', '1', '30000.00', '2018-11-22 18:25:29', '3000', '10', '3000', '0', '3万元金额,3千个小红包,每个10元');

建好的红包池表如下图

其中amount为红包池总金额,total为红包池总红包数,unit_amount为每个红包金额,stock为红包池剩余红包数。

 创建用户抢红包表 t_user_red_packet

-- ----------------------------
-- Table structure for t_user_red_packet
-- ----------------------------
DROP TABLE IF EXISTS `t_user_red_packet`;
CREATE TABLE `t_user_red_packet` (
  `id` int(12) NOT NULL AUTO_INCREMENT,
  `red_packet_id` int(12) NOT NULL,
  `user_id` int(12) NOT NULL,
  `amount` decimal(16,2) NOT NULL,
  `grap_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `note` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=240 DEFAULT CHARSET=utf8;

建好的抢红包表如下:

red_packet_id为红包池id,user_id为抢到红包的用户id,amount为抢到的金额,grap_time为抢到红包的时间。 

2.3 编写SQL

根据之前描述的抢红包逻辑,在抢红包之前先检查红包池,如果有剩余,则红包池剩余红包数减一,并新增一条抢到红包的记录。所以一共有三条sql:查看红包池、更新红包池库存、新增抢到红包记录。

在你的红包池映射器配置文件中加入以下sql

查看红包池sql

<select id="getRedPacket" parameterType="long" resultType="com.ssm.pojo.RedPacket">
	select id,user_id as userId,amount,send_date as sendDate,total,unit_amount as unitAmount,stock,version,note from T_RED_PACKET where id = #{id}
</select>

更新红包池库存sql

<update id="decreaseRedPacket" parameterType="long">
	update T_RED_PACKET set stock = stock-1 where id = #{id}
</update>

 在你的用户抢红包映射器配置文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值