java防止重复提交,第1张

请把小熊还给我&

已于 2022-07-04 23:20:15 修改

1359

 收藏 6

分类专栏: java面试 spring JAVA 文章标签: java 开发语言

版权

java面试

同时被 3 个专栏收录

10 篇文章1 订阅

订阅专栏

spring

11 篇文章1 订阅

订阅专栏

JAVA

3 篇文章0 订阅

订阅专栏

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

解决思路

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 {

    @Autowired

    private UserDao userDao;

    @Override

    @Transactional

    public  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 {

    @Resource

    RedisTemplate<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 {

    @Autowired

    private RedisLockUtil redisLockUtil;

    @Autowired

    private UserDao userDao;

    @Override

    @Transactional

    public  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

    @Transactional

    public  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));

                }

            }

————————————————

版权声明:本文为CSDN博主「请把小熊还给我&amp;」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » java防止重复提交

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情