题目

如何设计一个抽奖池,总抽奖金额5w元,里面有1元的,2元的,5元的面额等等。每个面额的有指定的次数限制,比如1元的10000次,2元的20000次,5元的2000次。
要求:

  1. 每个面额的次数不能超
  2. 总抽奖金额不能超
  3. 性能不能太差,比如不能设置一个全局锁,至少是用户级别的锁

设计思想

  1. 该场景主要需要解决高并发问题,
  2. 1元, 2元, 3元奖券, 以及金额池采用抽奖之前先冻结必要的奖券数和金额的方式, 之后抽奖后用不到的奖券和金额再释放掉.
  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:4private 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, boolean et2, boolean et5, int occupyMoney) {int number = 0;if (et1 && occupyMoney >= 1) {number += probability[0];}if (et2 && occupyMoney >= 2) {number += probability[1];}if (et5 && occupyMoney >= 5) {number += probability[2];}if (number == 0) {return 0;}int rdm = random.nextInt(number);if (et1 && occupyMoney >= 1) {if (rdm < probability[0]) {return 1;}rdm -= probability[0];}if (et2 && occupyMoney >= 2) {if (rdm < probability[1]) {return 2;}rdm -= probability[1];}if (et5 && occupyMoney >= 5 && rdm < probability[2]) {return 5;}throw new RuntimeException("计算异常");}/*** 占据一张奖票(1元)** @return 是否占用成功*/protected abstract boolean occupyMoney1();/*** 占据一张奖票(2元)** @return 是否占用成功*/protected abstract boolean occupyMoney2();/*** 占据一张奖票(5元)** @return 是否占用成功*/protected abstract boolean occupyMoney5();/*** 占据金额(若余额高于5元, 优先则占据5元, 否则若高于2元, 优先占据2元, 否则若高于1元, 则占据1元, 否则不占据任何金额, 返回0)** @return 占据金额*/protected abstract int occupyMoneyTotal();/*** 释放一张奖票(1元)*/protected abstract void releaseMoney1();/*** 释放一张奖票(2元)*/protected abstract void releaseMoney2();/*** 释放一张奖票(5元)*/protected abstract void releaseMoney5();/*** 释放金额* @param money 释放金额*/protected abstract void releaseMoneyTotal(int money);}

奖券的占用和释放的实现类

package cn.cpf.test;import lombok.ToString;import java.util.concurrent.atomic.AtomicInteger;/*** <b>Description : </b>** @author CPF* @date 2020/12/19 21:07**/
@ToString
public class AtomicLotteryMachine extends AbstractLotteryMachine {/*** 1元奖票数量*/private final AtomicInteger ticketNumber1 = new AtomicInteger(10000);private final AtomicInteger ticketNumber2 = new AtomicInteger(20000);private final AtomicInteger ticketNumber5 = new AtomicInteger(2000);/*** 奖金池*/private final AtomicInteger ticketMoneyTotal = new AtomicInteger(50000);/*** 占据一张奖票(1元)** @return 是否占用成功*/@Overrideprotected boolean occupyMoney1() {final int andDecrement = ticketNumber1.getAndDecrement();if (andDecrement > 0) {return true;}releaseMoney1();return false;}/*** 占据一张奖票(2元)** @return 是否占用成功*/@Overrideprotected boolean occupyMoney2() {final int andDecrement = ticketNumber2.getAndDecrement();if (andDecrement > 0) {return true;}releaseMoney2();return false;}/*** 占据一张奖票(5元)** @return 是否占用成功*/@Overrideprotected boolean occupyMoney5() {final int andDecrement = ticketNumber5.getAndDecrement();if (andDecrement > 0) {return true;}releaseMoney5();return false;}/*** 占据金额(若余额高于5元, 优先则占据5元, 否则若高于2元, 优先占据2元, 否则若高于1元, 则占据1元, 否则不占据任何金额, 返回0)** @return 占据金额*/@Overrideprotected int occupyMoneyTotal() {final int i = ticketMoneyTotal.get();if (i <= 0) {return 0;}final int andAdd = ticketMoneyTotal.addAndGet(-5);if (andAdd >= 0) {return 5;}if (andAdd >= -3) {releaseMoneyTotal(3);return 2;}if (andAdd >= -4) {releaseMoneyTotal(4);return 1;}releaseMoneyTotal(5);return 0;}/*** 释放一张奖票(1元)*/@Overrideprotected void releaseMoney1() {ticketNumber1.getAndIncrement();}/*** 释放一张奖票(2元)*/@Overrideprotected void releaseMoney2() {ticketNumber2.getAndIncrement();}/*** 释放一张奖票(5元)*/@Overrideprotected void releaseMoney5() {ticketNumber5.getAndIncrement();}/*** 释放金额** @param money 释放金额*/@Overrideprotected void releaseMoneyTotal(int money) {ticketMoneyTotal.getAndAdd(money);}}

测试类

package cn.cpf.test.d;import cn.cpf.test.AbstractLotteryMachine;
import cn.cpf.test.AtomicLotteryMachine;
import com.github.cosycode.common.util.otr.TestUtils;import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;/*** <b>Description : </b>** @author CPF* @date 2020/12/19 22:39**/
public class Test {public static void main(String[] args) {System.out.println(Charset.defaultCharset().name());AbstractLotteryMachine lotteryMachine = new AtomicLotteryMachine();System.out.println("未抽奖之前奖池情况" + lotteryMachine.toString());AtomicInteger t1 = new AtomicInteger();AtomicInteger t2 = new AtomicInteger();AtomicInteger t5 = new AtomicInteger();AtomicInteger tM = new AtomicInteger();TestUtils.callTime("抽奖测试", () -> {// 30000 次并发可以抽空奖池IntStream.range(1, 30000).parallel().forEach(it -> {final int lottery = lotteryMachine.lottery();switch (lottery) {case 1:t1.addAndGet(1);tM.addAndGet(1);break;case 2:t2.addAndGet(1);tM.addAndGet(2);break;case 5:t5.addAndGet(1);tM.addAndGet(5);break;case 0:break;default:throw new RuntimeException("default");}});});System.out.println("抽奖之后奖池情况" + lotteryMachine.toString());System.out.println("1元奖券出现次数 " + t1);System.out.println("2元奖券出现次数 " + t2);System.out.println("5元奖券出现次数 " + t5);System.out.println("抽奖金额 " + tM);}}

输出

使用并行流30000次单机模拟抽奖测试 27毫秒, 数据正常.

未抽奖之前奖池情况AtomicLotteryMachine(ticketNumber1=10000, ticketNumber2=20000, ticketNumber5=2000, ticketMoneyTotal=50000)
[INFO] 02:44:40 (TestUtils.java:42)=> [624315079342100 : 抽奖测试] ==> start
[INFO] 02:44:40 (TestUtils.java:45)=> [624315079342100 : 抽奖测试] ==> end, consume time: 27538200
抽奖之后奖池情况AtomicLotteryMachine(ticketNumber1=1549, ticketNumber2=2983, ticketNumber5=497, ticketMoneyTotal=0)
1元奖券出现次数 8451
2元奖券出现次数 17017
5元奖券出现次数 1503
抽奖金额 50000

TestUtils.java

package com.github.cosycode.common.util.otr;import lombok.extern.slf4j.Slf4j;import java.util.function.Supplier;/*** <b>Description : </b>** @author CPF* @since 1.0* @date 2020/11/26 17:41**/
@Slf4j
public class TestUtils {private TestUtils(){}/*** 正常执行 Runnable 函数, 打印 执行 Runnable 函数的执行时长信息** @param tag 标记* @param runnable 执行函数*/public static void callTime(String tag, Runnable runnable) {callTime(tag, () -> {runnable.run();return true;});}/*** 正常执行 supplier 函数, 打印 执行 supplier 函数的执行时长信息** @param tag 标记* @param supplier 执行函数* @param <R> 执行函数返回值类型* @return 执行函数的返回值*/public static <R> R callTime(String tag, Supplier<R> supplier) {long start = System.nanoTime();log.info("[{} : {}] ==> start", start, tag);R r = supplier.get();long inVal = System.nanoTime() - start;log.info("[{} : {}] ==> end, consume time: {} ", start, tag, inVal);return r;}}

面试题目:一个抽奖池设计(含代码)相关推荐

  1. Java——有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300}

    有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300}; 创建两个抽奖箱(线程)设置线程名称分别 ...

  2. 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300};创建两个抽奖箱(线程)...

    有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300};     创建两个抽奖箱(线程)设置线程 ...

  3. 物流信息管理系统MySQL设计,物流管理系统的SQL数据库设计(含代码)

    物流管理系统的SQL数据库设计(含代码) 物流管理信息系统的数据库设计班级xxx系统名称:物流管理信息系统一.需求分析物流管理系统是为制造商和零售商设计的管理系统数据库系统,目的是:1.实现上游制造商 ...

  4. 用html实现抽奖大转盘,【项目实战】用CSS实现一个抽奖转盘(附详细代码+思路)...

    原标题:[项目实战]用CSS实现一个抽奖转盘(附详细代码+思路) 效果 基本是用CSS实现的,没有用图片,加一丢丢JS. 完全没有考虑兼容性. 首先画一个转盘 < htmllang= " ...

  5. html旋转代码_用CSS实现一个抽奖转盘(附详细代码+思路)

    原文:https://www.cnblogs.com/wenruo/p/9732704.html 先上效果 基本是用CSS实现的,没有用图片,加一丢丢JS.不过没有考虑太多兼容性. 首先画一个转盘 & ...

  6. 用 CSS 实现一个抽奖转盘(附详细代码+思路)

    关注公众号 前端开发博客,回复"加群" 加入我们一起学习,天天进步 作者:我不吃饼干呀 https://www.cnblogs.com/wenruo/p/9732704.html ...

  7. 用CSS实现一个抽奖转盘(附详细代码+思路)

    作者:我不吃饼干呀 https://www.cnblogs.com/wenruo/p/9732704.html 基本是用CSS实现的,没有用图片,加一丢丢JS.完全没有考虑兼容性. 首先画一个转盘 & ...

  8. 面试官一个线程池问题把我问懵逼了。

    你好呀,我是why哥. 前几天,有个朋友在微信上找我.他问:why哥,在吗? 我说:发生肾么事了? 他啪的一下就提了一个问题啊,很快. 我大意了,随意瞅了一眼,这题不是很简单吗? 结果没想到里面还隐藏 ...

  9. Android 面试题目之 线程池

    [color=red][size=xx-large]记不住密码怎么办?[/size][/color] [url]http://a.app.qq.com/o/simple.jsp?pkgname=com ...

最新文章

  1. microsoft 为microbit.org 设计的课程
  2. 一、网络爬虫----爬取豆瓣网前250条数据
  3. ai模仿声音软件_如何开发人工智能类的软件?人工智能让我们的生活更加便捷!...
  4. filter2D函数
  5. Windows中32位(x86)和64位(x64)解释
  6. Bean 在 Spring 中代表什么含义,为什么这样命名?
  7. 2个css特效冲突了怎么办_学生打扫卫生不积极怎么办?改变自己的观念,从2个方面影响学生...
  8. oracle 12.2.0.1 使用 active dataguard broker 之二 switchover
  9. [Z]在线版本控制之SubVersion与MyEclipse整合
  10. ecshop 影响全局的标量lib_main.php
  11. 学习Linux让我进入了知名企业 原
  12. oracle-Nomount
  13. gcc 源码下载地址
  14. 2021-05-22
  15. Facebook自动化测试札记
  16. 微信小程序实现图片下载功能
  17. Manjaro Linux 双显卡安装步骤及独立显卡运行游戏(Nvidia GeForce GTX 980m)
  18. 带宽、符号速率、比特率、码元速率、采样速率之间的关系
  19. 全志H6 Android 7.0 平台 修改横竖屏显示
  20. .shape()与.reshape()函数

热门文章

  1. 删除91助手右键菜单
  2. 2016年上半年信息系统项目管理师真题之上午题小虎趣味解答第26-30题
  3. webpack配置(webpack配置文件)
  4. 【天体学】基于matlab GUI太阳天顶角计算【含Matlab源码 2229期】
  5. C语言中的整数(short,int,long)
  6. PHP生成用户邀请码
  7. ProE拉伸特征总结
  8. Delphi 繁简转换
  9. Hadoop详细集群搭建
  10. 网络优化-HttpDns