公司有个抽奖活动每人一天只能抽取一次,有用户恶意短时间内重复提交多次导致抽奖多发情况

解决思路

1.创建一个map集合存储每个用户对象作为对象锁,存储用户对象时要采用双重校验锁保证唯一性
2.在控制层加同步代码块,不能在业务层加因为事务会导致同步代码块失效
3.抽奖完成后进行把用户给移除掉释放内存

1.工具类

package com.yujie.utils;import com.yujie.model.User;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class MapConcurrent {private static Map<String, User> userMap= new ConcurrentHashMap<>();public static User getUser(String name){//双重校验if (!userMap.containsKey(name)) {synchronized (MapConcurrent.class){if (!userMap.containsKey(name)) {User user = new User();user.setUserName(name);userMap.put(name,user);return user;}}}return userMap.get(name);}public static void removeUser(String name){userMap.remove(name);}
}

2.controller层,模拟用户抽奖代码

@RequestMapping("/consume")public String consume(){//伪造前端数据User user1 = new User();user1.setUserName("王大宝");user1.setId(1);user1.setNum(1);User u = MapConcurrent.getUser(user1.getUserName());System.out.println("当前对象锁"+u.hashCode());//加锁并发安全synchronized (u){userService.consume(user1);//抽奖结束销毁用户对象MapConcurrent.removeUser(user1.getUserName());}return "抽奖成功";}

2.service层代码

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Override@Transactionalpublic  void consume(User user) {System.out.println(Thread.currentThread().getName());User user1 = selectUserById(user.getId());if(user1 != null){Integer num = user1.getNum();if(num != null && num>0){user1.setNum(num-1);userDao.save(user1);System.err.println("100元话费卡-卡号:"+new Random().nextInt(99999999)+"密码:"+new Random().nextInt(99999999) +",剩余抽奖次数:"+(num-1));}}}

以下是模拟用户10次并发提交抽奖

3.没加锁前运行效果,导致一次抽奖发了10次奖品的bug

当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
当前对象锁1743829393
http-nio-8080-exec-8
http-nio-8080-exec-5
http-nio-8080-exec-10
http-nio-8080-exec-9
http-nio-8080-exec-7
http-nio-8080-exec-4
http-nio-8080-exec-6
http-nio-8080-exec-1
http-nio-8080-exec-3
http-nio-8080-exec-2
100元话费卡-卡号:30879231密码:6585717,剩余抽奖次数:0
100元话费卡-卡号:92273305密码:14406295,剩余抽奖次数:0
100元话费卡-卡号:38463818密码:4953953,剩余抽奖次数:0
100元话费卡-卡号:1038147密码:34559577,剩余抽奖次数:0
100元话费卡-卡号:56007678密码:41413612,剩余抽奖次数:0
100元话费卡-卡号:33740124密码:81815012,剩余抽奖次数:0
100元话费卡-卡号:9332570密码:19397480,剩余抽奖次数:0
100元话费卡-卡号:36659655密码:58524451,剩余抽奖次数:0
100元话费卡-卡号:6957685密码:98985577,剩余抽奖次数:0
100元话费卡-卡号:71413799密码:29059920,剩余抽奖次数:0

4.加锁后运行效果

当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
当前对象锁2137528174
http-nio-8080-exec-3
100元话费卡-卡号:58114881密码:79931529,剩余抽奖次数:0
http-nio-8080-exec-2
http-nio-8080-exec-1
http-nio-8080-exec-5
http-nio-8080-exec-4
http-nio-8080-exec-10
http-nio-8080-exec-7
http-nio-8080-exec-9
http-nio-8080-exec-8
http-nio-8080-exec-6

方式二采用redis setnx解决

package com.yujie.utils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.Objects;/*** @ClassName RedisLockUtil* @Description 使用redis做锁* @Author Wangyujie* @Version V1.1.0*/
@Component
public class RedisLockUtil {@ResourceRedisTemplate<String, Object> redisTemplate;/*** 获取锁,true 则得到锁,false 已被锁定* @param lockName       锁名称* @param lockExoire     锁时间毫秒* @return*/public Boolean getLock(String lockName, Integer lockExoire) {return (Boolean) redisTemplate.execute((RedisCallback<?>) connection -> {// 获取时间毫秒值long expireAt = System.currentTimeMillis() + lockExoire + 1;// 获取锁Boolean acquire = connection.setNX(lockName.getBytes(), String.valueOf(expireAt).getBytes());if (acquire) {return true;} else {byte[] bytes = connection.get(lockName.getBytes());// 非空判断if (Objects.nonNull(bytes) && bytes.length > 0) {long expireTime = Long.parseLong(new String(bytes));// 如果锁已经过期if (expireTime < System.currentTimeMillis()) {// 重新加锁,防止死锁byte[] set = connection.getSet(lockName.getBytes(),String.valueOf(System.currentTimeMillis() + lockExoire + 1).getBytes());return Long.parseLong(new String(set)) < System.currentTimeMillis();}}}return false;});}/*** 删除锁* @param lockName*/public void delLock(String lockName) {redisTemplate.delete(lockName);}/*** 获取锁Key* @param prefix    前缀* @param name      名称* @return*/public static String getFullKey(String prefix, String name) {return prefix + "_" + name;}}

在service层使用

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate RedisLockUtil redisLockUtil;@Autowiredprivate UserDao userDao;@Override@Transactionalpublic  void consume(User user) {Boolean lock = redisLockUtil.getLock(user.getUserName(), 20000);if (lock){System.out.println(Thread.currentThread().getName());User user1 = selectUserById(user.getId());if(user1 != null){Integer num = user1.getNum();if(num != null && num>0){user1.setNum(num-1);userDao.save(user1);System.err.println("100元话费卡-卡号:"+new Random().nextInt(99999999)+"密码:"+new Random().nextInt(99999999) +",剩余抽奖次数:"+(num-1));}}}}
}

方式三:使用数据库的FOR UPDATE ,当查询这条数据的时候就上行锁,其他线程就查询不了

查询语句加上FOR UPDATE
SELECT * FROM t_user WHERE id=1 FOR UPDATE ;

service层上加上事务注解即可

    @Override@Transactionalpublic  void consume(User user) {System.out.println(Thread.currentThread().getName());User user1 = selectUserById(user.getId());if(user1 != null){Integer num = user1.getNum();if(num != null && num>0){user1.setNum(num-1);userDao.save(user1);System.err.println("100元话费卡-卡号:"+new Random().nextInt(99999999)+"密码:"+new Random().nextInt(99999999) +",剩余抽奖次数:"+(num-1));}}}

java防止重复提交相关推荐

  1. java订单重复提交_java表单重复提交常用解决办法

    最近在看些基础的东西,顺便做下笔记.相信大家在平时网页使用中,经常会有按钮重复点击,然后点不动刷新,还有当网络延时比较厉害点了没反应在点击的重复提交.为了避免这种情况,总结了一下4点处理方案 表单重复 ...

  2. java 防止重复提交

    放在重复提交: 1.页面多次点击按钮 解决办法:javascript控制提交,将submit改成button提交 <script type="text/javascript" ...

  3. 使用拦截器防止表单重复提交

    业务场景介绍 web系统经常会出现用户在页面上快速点击多次提交按钮(或者重复刷新页面),在后台会连续接收多次请求,除了第一次外,其他的相同请求就是重复提交. 如何避免页面重复提交呢,正常有以下几种方法 ...

  4. java 反正多次重复提交_java web开发时防止刷新后的重复提交

    在java web开发过程中大家经常都会遇到页面刷新后重复提交导致数据库数据重复的情况出现. 那么,如何避免重复提交数据的情况出现呢?如下代码,是在jsp中解决重复提交的一种方式. //此段代码用于防 ...

  5. java怎么防止表单重复提交_如何防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  6. Java使用Redis实现分布式锁来防止重复提交问题

    如何用消息系统避免分布式事务? - 少年阿宾 - BlogJava http://www.blogjava.net/stevenjohn/archive/2018/01/04/433004.html ...

  7. java mysql防重复提交_防止数据重复提交的6种方法(超简单)!

    有位朋友,某天突然问磊哥:在 Java 中,防止重复提交最简单的方案是什么? 这句话中包含了两个关键信息,第一:防止重复提交:第二:最简单. 于是磊哥问他,是单机环境还是分布式环境? 得到的反馈是单机 ...

  8. java mvc中重复提交表单,spring mvc 防止重复提交表单的两种方法,推荐第二种

    第一种方法:判断session中保存的token 比较麻烦,每次在提交表单时都必须传入上次的token.而且当一个页面使用ajax时,多个表单提交就会有问题. 注解Token代码: package c ...

  9. java客户端重复请求_Java后台防止客户端重复请求、提交表单实现原理

    Java后台防止客户端重复请求.提交表单实现原理 发布于 2021-1-8| 复制链接 摘记: 这篇文章主要介绍了Java后台防止客户端重复请求.提交表单实现原理,文中通过示例代码介绍的非常详细,对大 ...

最新文章

  1. linux中ed编辑器手册,脚本编辑器 - Navicat 15 for Linux 产品手册
  2. IPv6 auto config 原理详解之-----前缀公告
  3. vb初学者编程代码大全_VB编程应该如何学习?
  4. 初学者万年历c语言源代码,C语言万年历的源程序
  5. 使用js在桌面上写一个倒计时器_论一个倒计时器的性能优化之路
  6. java 统计单词个数和标点符号
  7. yocto生成各种格式的文件系统
  8. html target=_blank 弹出独立窗口,HTML base 标签的 target 属性 —— base target=_blank /...
  9. ubuntu 安装多个CUDA版本并可以随时切换
  10. spark中的广播变量与累加器
  11. kafka权威指南-笔记
  12. 【机器学习笔记1】一元线性回归模型及预测
  13. IT笔试题收集,免费下载
  14. 树莓派官方显示屏亮度
  15. 算法—— LeetCode 第一遍
  16. 大数据服务模型设计:默默无闻的贤内助
  17. oracle与sun的java_甲骨文吞Sun Java何去何从?
  18. Ubuntu打造家用NAS二——服务器管理
  19. Python学习之求绝对值的几种方法
  20. 多任务环境中如何喂看门狗?

热门文章

  1. 以太坊测试网络rinkeby交易测试
  2. 快递查询—API接口
  3. 2022爱分析・工业互联网实践报告
  4. 46招健脑秘笈,让你变得更聪明
  5. 目标和学习方法的重要性
  6. bmob云服务器信息推送,FAQ-Bmob后端云
  7. 神经网络Neural Networks概述
  8. 长链接短链接拉起拼多多问题
  9. -XX:SoftRefLRUPolicyMSPerMB从名字看不出什么意思?【官文解读】
  10. 扩展欧几里得算法 求解 丢番图方程