一.目标
多人抢红包时保证红包金额金和红包个数的准确,同时保证并发性。
二.方案
- 可以采用java中的锁机制来控制每个抢红包线程对红包的获取,保证同时只有一个线程在抢红包,同时利用volitate关键子保证红包金额和红包个数对所有线程可见性(java内存模型)。
- 通过乐观锁实现更高的并发性。抢红包过程中涉及到两个共享红包金额sum和红包金额sum和红包个数count两个变量,要同时控制两个共享变量的原子性。
三.实施方案
采用乐观锁实现多人抢红包算法,可以保证更高的并发性。由于要同时保证两个共享变量的原子性,有两种方案:
- 可以将红包金额和红包个数包装在一个对象里,采用AtomicReference保证对象的原子性从而保证红包金额和红包个数的原子性。
- 通过AtomicStampedReference来实现,版本号初始化为红包个数,在这个场景中,每次抢依次红包改变不是加1,而是减1。
第二中方案比较简单,因此我才用了第二种方案来实现。构造函数和红包中变量定义代码如下:
//每个线程可以抢当前红包余额的最大比例
private final double MAX_RATE=0.6;
private final double MIN_MONEY=0.1;
//控制红包金额和红包个数的原子性,红包个数是版本号 AtomicStampedReference<BigDecimal> pari;
//红包个数
private Integer count;
//红包金额private BigDecimal sum;
//测试用,红包总金额是否有错
static BigDecimal sumMoney=new BigDecimal(0.0);
//红包的构造函数
public RedPacket(int count,double sum)
{ this.count=count;
this.sum=new BigDecimal(sum);
this.pari=new AtomicStampedReference<BigDecimal>(this.sum,count);
}一个抢红包过程的的实现代码:
public BigDecimal getMoney()
{
//当还有红包时线程不断去尝试抢红包,直到抢到红包或者被抢完退出
while(true) {
BigDecimal expectSum = this.pari.getReference();
int expectCount = this.pari.getStamp();
if (expectCount<1) {
System.out.println("手速太慢,红包已经被抢完啦!");
return new BigDecimal(0);
} else if (expectCount== 1) {
if (this.pari.compareAndSet(expectSum,new BigDecimal(0),expectCount,expectCount-1)) {
System.out.println("恭喜你抢到了最后一个红包" + expectSum+ "元");
return expectSum;
}
} else {
BigDecimal rate = new BigDecimal(Math.random() * MAX_RATE).setScale(1,BigDecimal.ROUND_HALF_DOWN);
BigDecimal result=expectSum.multiply(rate);
if(result.doubleValue()<MIN_MONEY)
{
result=new BigDecimal(0.1);
}
if (this.pari.compareAndSet(expectSum,expectSum.subtract(result),expectCount,expectCount-1)) {
System.out.println("恭喜你抢到了" + result+ "元");
return result;
}
}
}
}测试:
public static void main(String[] args) throws InterruptedException {
//20个人抢
int people=20;
//这个锁纯粹为了验证每轮红包结束后金额是否正确,不影响每一轮的抢红包过程
Lock lock=new ReentrantLock();
//进行30轮测试
for(int j=0;j<30;j++) {
//红包个数5,红包金额100
RedPacket redPacket=new RedPacket(5,100.0);
//用于一轮抢红包结束前,不进行新一轮的抢红包。(测试才需要)
CountDownLatch latch=new CountDownLatch(people);
System.out.println("抢红包开始啦===================================");
for (int i = 1; i <= people; i++) {
new Thread(() -> {
BigDecimal num = redPacket.getMoney();
lock.lock();
sumMoney=sumMoney.add(num);
lock.unlock();
latch.countDown();
}).start();
}
latch.wait();
System.out.println("抢红包结束啦=========总金额 "+sumMoney.doubleValue()+"=============================");
sumMoney=sumMoney.subtract(sumMoney);
}
}结果:
完整代码见------github源码