题目
如何设计一个抽奖池,总抽奖金额5w元,里面有1元的,2元的,5元的面额等等。每个面额的有指定的次数限制,比如1元的10000次,2元的20000次,5元的2000次。
要求:
- 每个面额的次数不能超
- 总抽奖金额不能超
- 性能不能太差,比如不能设置一个全局锁,至少是用户级别的锁
设计思想
- 该场景主要需要解决高并发问题,
- 1元, 2元, 3元奖券, 以及金额池采用
抽奖之前先冻结必要的奖券数和金额
的方式, 之后抽奖后用不到的奖券和金额
再释放掉. - 最初考虑bitmap来着, 但是bitmap的占用和释放以及遍历每每都是麻烦, 最后采用AtomicInteger的CAS的方式解决并发量和多线程问题.
代码
先编写抽奖相关的函数, 至于抽奖池以及奖券的占用和释放先写为抽象函数, 之后慢慢实现
package cn.cpf.test;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
/**
* <b>Description : </b>
*
* @author CPF
* @date 2020/12/19 20:27
**/
@Slf4j
public abstract class AbstractLotteryMachine {
private final Random random = new Random();
// 抽奖概率, 1元, 2元, 5元的概率比重为 22:44:4
private final int[] probability = new int[]{
22, 44, 4};
/**
* 抽奖方法
*
* @return 抽奖金额(1: 1元, 2: 2元, 5: 5元, 0: 没奖了)
*/
public int lottery() {
boolean et1 = false;
boolean et2 = false;
boolean et5 = false;
int occupyMoney = 0;
int money = 0;
try {
occupyMoney = occupyMoneyTotal();
if (occupyMoney == 0) {
return money;
}
assert occupyMoney > 0;
et1 = occupyMoney1();
et2 = occupyMoney2();
et5 = occupyMoney5();
money = randomMoney(et1, et2, et5, occupyMoney);
assert money <= occupyMoney;
return money;
} catch (RuntimeException e){
log.error("et1: {}, et2: {}, et3:{}, occ:{}, money: {}", et1, et2, et5, occupyMoney, money);
throw e;
} finally {
if (et1 && money != 1) {
releaseMoney1();
}
if (et2 && money != 2) {
releaseMoney2();
}
if (et5 && money != 5) {
releaseMoney5();
}
assert occupyMoney >= money;
if (occupyMoney > 0) {
final int releaseMoney = occupyMoney - money;
releaseMoneyTotal(releaseMoney);
}
}
}
/**
* 10000张1元 + 20000张2元 刚好等于 50000, 也就是说最优的情况是刚好抽奖抽了10000张1元 和 20000张2元,
* 5元的奖券抽中的次数越少越节约成本, 1元, 2元的券之间概率调整没什么意义
*
* @param et1 1元奖券是否存在
* @param et2 2元奖券是否存在
* @param et5 5元奖券是否存在
* @param occupyMoney 占据金额
* @return 抽取的奖券 0:没有合适的奖券了, 1: 1元, 2: 2元, 5: 5元
*/
protected int randomMoney(boolean et1, boole