竞态条件?如何设计一个抢红包的程序? 说说你的思路

笔者在前两天参加面试的时候被问到了一个场景问题,觉得自己之前准备的确实不妥当,在此抛砖引玉分享思路。


背景了解

首先明确问题,100个人的群组里让你去发一个红包,可以被88个人抢。那么1. 你怎么解决他们争抢的问题。2. 你怎么决绝红包金额分配问题,同时要让红包分配的金额有趣。

竞态条件

在程序开发中,竞态条件指的是多个线程同时访问共享资源时,由于没有采用合适的同步机制,导致结果的正确性无法保证。在社交平台项目开发中,竞态条件可能会出现在以下场景中:

  1. 用户登录状态:当多个用户同时访问同一个用户的个人主页时,可能会出现竞态条件。例如,当一个用户登录时,其他用户可能会同时访问该用户的个人主页,如果没有采用合适的同步机制,可能会出现用户信息不一致的问题。
  2. 用户消息列表:当多个用户同时刷新自己的消息列表时,可能会出现竞态条件。例如,当一个用户刷新自己的消息列表时,其他用户也可能会同时刷新该用户的消息列表,如果没有采用合适的同步机制,可能会出现消息列表不一致的问题。
  3. 文件读写:当多个线程同时读写同一个文件时,可能会出现竞态条件。例如,当多个用户同时编辑同一个文档时,如果没有采用合适的同步机制,可能会出现文档内容不一致的问题。

在以上场景中,我们可以采用线程安全的同步机制,例如synchronized关键字、ReentrantLock类、CountDownLatch类、Semaphore类等,来避免竞态条件的产生。同时,我们还可以采用合适的并发策略,例如线程池、信号量等,来提高系统的并发性和性能。

解决思路

处理争抢问题

当时脑海中最初出现的就是用Redis实现分布式锁的方式来处理争抢问题。

比如可以生成一段参考代码,这里的红包金额分配仅仅采用平均值,不够有趣。但是能保证争抢问题得到改善。

看参考资料的描述

问:并发性处理:红包如何计算被抢完?

答:cache会抵抗无效请求,将无效的请求过滤掉,实际进入到后台的量不大。cache记录红包个数,原子操作进行个数递减,到 0 表示被抢光。财付通按照 20万笔每秒入账准备,但实际还不到 8万每秒

public class GrabRedPackService {
    private static final String REDPACK_KEY = "grab_red_pack";
    private static final String USER_LOCK_KEY = "user_lock";

    public static int grabRedPackService(String redpackId, String userId) {
        // 尝试获取分布式锁
        String lockValue = UUID.randomUUID().toString();
        int result = jedisClient.setNX(USER_LOCK_KEY, lockValue);
        if (result == 1) {
            // 获取成功,执行红包分配逻辑
            Map redPackMap = jedisClient.hgetall(REDPACK_KEY + ":" + redpackId);
            BigDecimal totalAmount = (BigDecimal) redPackMap.get("total_amount");
            int totalUsers = (Integer) redPackMap.get("total_users");
            BigDecimal userAmount = new BigDecimal(totalAmount.doubleValue() / totalUsers);
            String[] users = (String[]) redPackMap.get("users");
            Arrays.sort(users);
            int i = users.length - 1;
            for (String user : users) {
                if (user.equals(userId)) {
                    // 抢到红包,将分配结果存储回Redis中
                    jedisClient.hset(REDPACK_KEY + ":" + redpackId, "user_" + i, userAmount.toString());
                    return 1;
                }
                i--;
            }
            // 没有抢到红包,释放分布式锁
            jedisClient.del(USER_LOCK_KEY);
            return 0;
        } else {
            // 获取失败,返回“fail”或者其他特殊返回值
            return 0;
        }
    }
}

如果有需求,也可以改代码为while(true)+超时的乐观锁机制不断重试。 

处理红包金额有趣的问题

1、抢红包的过程 2、随机法 3、均值法。具体可参考下方的参考资料。


参考资料

互斥锁,同步锁,临界区,互斥量,信号量,自旋锁之间联系是什么? - 胖君的回答 - 知乎 https://www.zhihu.com/question/39850927/answer/242109380

最全解密微信红包随机算法(含代码实现)-腾讯云开发者社区-腾讯云

【大厂面试题】如何设计一个抢红包算法?_哔哩哔哩_bilibili

Redis乐观锁解决高并发抢红包的问题【redis】-腾讯云开发者社区-腾讯云

你可能感兴趣的:(面试,Java,java,jvm,开发语言)