Java多线程实现抢红包功能详解 - 从原理到代码实现

最近在复习Java多线程编程时,做了一个简单案例,抢红包。在日常中微信的抢红包大家肯定都经历过,接下来我们就用Java的多线程编程来模拟一下微信的抢红包功能。

功能需求分析

一个完整的抢红包功能需要实现以下核心功能:

  1. 红包分配:将总金额随机分成若干份

  2. 多线程抢红包:模拟多人同时抢红包的场景

  3. 统计结果:找出抢到金额最多的"运气王"

1. 红包分配算法

// 分配红包
    public List<BigDecimal> divideRedPacket(BigDecimal totalMoney, int totalNum) {
        List<BigDecimal> redPacketList = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < totalNum - 1; i++) {
            // 每个人能抢到的最多的钱 总钱数-还剩下要封装的红包个数*0.01
            BigDecimal maxMoney = totalMoney.subtract(new BigDecimal(totalNum - i - 1).multiply(new BigDecimal("0.01")));
            // 实际上他抢到的钱
            BigDecimal money =
                    new BigDecimal("0.01")                   // 红包最少金额
                    .add(new BigDecimal(r.nextDouble())          // r.nextDouble() 生成0-1之间的随机数(用红包最少金额来加就是为了避免生成0时,红包金额为0)
                    .multiply(maxMoney))
                    .setScale(2, RoundingMode.HALF_UP); // 保留两位小数,四舍五入
            redPacketList.add(money);
            totalMoney = totalMoney.subtract(money);
        }
        // 最后一个红包 直接封装剩余的钱
        redPacketList.add(totalMoney);
        return redPacketList;
    }

这段代码实现了红包的随机分配

  1. 使用BigDecimal进行精确的金额计算,避免浮点数精度问题

  2. 每次分配时,计算当前可分配的最大金额:总金额-剩余红包数*0.01(保证每个红包至少有0.01元)

  3. 生成随机金额:0.01 + 随机数*最大可分配金额

  4. 设置金额精度为2位小数,使用四舍五入

  5. 最后一个红包直接取剩余金额

2. 多线程抢红包实现 (模拟多人抢红包)

// 抢红包
    public void getRedPacket(List<BigDecimal> redPacketList) {
        synchronized (obj){
            if (redPacketList.size() != 0){
                // 红包还没抢完
                // 抢到的钱
                BigDecimal getMoney = redPacketList.remove(0);
                System.out.println(Thread.currentThread().getName() + "抢到了" + getMoney + "元");
                map.put(Thread.currentThread().getName(), getMoney);
            }else{
                // 红包抢完了
                System.out.println(Thread.currentThread().getName() + "没抢到红包");
            }
        }
    }

关键点:

  1. 使用synchronized关键字保证线程安全

  2. 检查红包列表是否为空

  3. 从列表中取出第一个红包

  4. 将抢红包结果存入Map,用于后续统计运气王

3. 运气王统计

// 运气王
    public void printLuckiest() {
        map.entrySet().stream()
                .max((o1, o2) -> o1.getValue().compareTo(o2.getValue()))
                .ifPresent(o -> System.out.println("运气王是" + o.getKey() + "抢到了" + o.getValue() + "元"));
    }

使用Java 8的Stream API(大家可以自己多了解一下Stream API)简洁地找出抢到金额最多的用户。

4. 完整代码展示

package com.tsj.JUC.Paritce;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

public class redPacket {
    // 红包列表
    private final List<BigDecimal> redPacketList = new ArrayList();
    private final Object obj = new Object();
    private final Map<String, BigDecimal> map = new HashMap<>();

    // 分配红包
    public List<BigDecimal> divideRedPacket(BigDecimal totalMoney, int totalNum) {
        List<BigDecimal> redPacketList = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < totalNum - 1; i++) {
            // 每个人能抢到的最多的钱 总钱数-还剩下要封装的红包个数*0.01
            BigDecimal maxMoney = totalMoney.subtract(new BigDecimal(totalNum - i - 1).multiply(new BigDecimal("0.01")));
            // 实际上他抢到的钱
            BigDecimal money =
                    new BigDecimal("0.01")                   // 红包最少金额
                    .add(new BigDecimal(r.nextDouble())          // r.nextDouble() 生成0-1之间的随机数(用红包最少金额来加就是为了避免生成0时,红包金额为0)
                    .multiply(maxMoney))
                    .setScale(2, RoundingMode.HALF_UP); // 保留两位小数,四舍五入
            redPacketList.add(money);
            totalMoney = totalMoney.subtract(money);
        }
        // 最后一个红包 直接封装剩余的钱
        redPacketList.add(totalMoney);
        return redPacketList;
    }

    // 抢红包
    public void getRedPacket(List<BigDecimal> redPacketList) {
        synchronized (obj){
            if (redPacketList.size() != 0){
                // 红包还没抢完
                // 抢到的钱
                BigDecimal getMoney = redPacketList.remove(0);
                System.out.println(Thread.currentThread().getName() + "抢到了" + getMoney + "元");
                map.put(Thread.currentThread().getName(), getMoney);
            }else{
                // 红包抢完了
                System.out.println(Thread.currentThread().getName() + "没抢到红包");
            }
        }
    }

    // 运气王
    public void printLuckiest() {
        map.entrySet().stream()
                .max((o1, o2) -> o1.getValue().compareTo(o2.getValue()))
                .ifPresent(o -> System.out.println("运气王是" + o.getKey() + "抢到了" + o.getValue() + "元"));
    }

    public static void main(String[] args) {
        redPacket rp = new redPacket();
        // 发红包
        List<BigDecimal> redPacket = rp.divideRedPacket(new BigDecimal("100"), 3);
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                rp.getRedPacket(redPacket);
            }).start();
        }

        // 必须等所有抢红包线程都结束后,再执行(主线程可能在抢红包线程执行结束前就执行了printLuckiest,导致map为空或数据不全)
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        rp.printLuckiest();
    }
}

总结

通过这个案例,我们学习了:

  1. 使用BigDecimal进行精确金融计算

  2. 多线程环境下的资源共享与同步

  3. 随机分配算法的实现

  4. Java 8 Stream API的高级用法

实际开发中,还需要考虑更多边界条件和性能优化,但这个实现已经涵盖了核心功能。希望这篇文章能帮助你理解多线程编程和红包算法的实现原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值