最近在复习Java多线程编程时,做了一个简单案例,抢红包。在日常中微信的抢红包大家肯定都经历过,接下来我们就用Java的多线程编程来模拟一下微信的抢红包功能。
功能需求分析
一个完整的抢红包功能需要实现以下核心功能:
红包分配:将总金额随机分成若干份
多线程抢红包:模拟多人同时抢红包的场景
统计结果:找出抢到金额最多的"运气王"
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;
}
这段代码实现了红包的随机分配:
使用BigDecimal进行精确的金额计算,避免浮点数精度问题
每次分配时,计算当前可分配的最大金额:总金额-剩余红包数*0.01(保证每个红包至少有0.01元)
生成随机金额:0.01 + 随机数*最大可分配金额
设置金额精度为2位小数,使用四舍五入
最后一个红包直接取剩余金额
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() + "没抢到红包");
}
}
}
关键点:
使用synchronized关键字保证线程安全
检查红包列表是否为空
从列表中取出第一个红包
将抢红包结果存入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();
}
}
总结
通过这个案例,我们学习了:
使用BigDecimal进行精确金融计算
多线程环境下的资源共享与同步
随机分配算法的实现
Java 8 Stream API的高级用法
实际开发中,还需要考虑更多边界条件和性能优化,但这个实现已经涵盖了核心功能。希望这篇文章能帮助你理解多线程编程和红包算法的实现原理。

2585

被折叠的 条评论
为什么被折叠?



