面試題目:一個抽獎池設計(含程式碼)
題目
如何設計一個抽獎池,總抽獎金額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();
/**
* 抽獎方法
*
* @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) {
final int[] probability = new int[]{22, 44, 4};
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 是否佔用成功
*/
@Override
protected boolean occupyMoney1() {
final int andDecrement = ticketNumber1.getAndDecrement();
if (andDecrement > 0) {
return true;
}
releaseMoney1();
return false;
}
/**
* 佔據一張獎票(2元)
*
* @return 是否佔用成功
*/
@Override
protected boolean occupyMoney2() {
final int andDecrement = ticketNumber2.getAndDecrement();
if (andDecrement > 0) {
return true;
}
releaseMoney2();
return false;
}
/**
* 佔據一張獎票(5元)
*
* @return 是否佔用成功
*/
@Override
protected boolean occupyMoney5() {
final int andDecrement = ticketNumber5.getAndDecrement();
if (andDecrement > 0) {
return true;
}
releaseMoney5();
return false;
}
/**
* 佔據金額(若餘額高於5元, 優先則佔據5元, 否則若高於2元, 優先佔據2元, 否則若高於1元, 則佔據1元, 否則不佔據任何金額, 返回0)
*
* @return 佔據金額
*/
@Override
protected 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元)
*/
@Override
protected void releaseMoney1() {
ticketNumber1.getAndIncrement();
}
/**
* 釋放一張獎票(2元)
*/
@Override
protected void releaseMoney2() {
ticketNumber2.getAndIncrement();
}
/**
* 釋放一張獎票(5元)
*/
@Override
protected void releaseMoney5() {
ticketNumber5.getAndIncrement();
}
/**
* 釋放金額
*
* @param money 釋放金額
*/
@Override
protected 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
相關文章
- 抽獎小程式資料庫設計資料庫
- 最新天貓面試題(含總結):執行緒池+併發程式設計+分散式設計+中介軟體面試題執行緒程式設計分散式
- 半小時擼一個抽獎程式
- iOS面試·一個iOS程式設計師的BAT面試全記錄(內含百度+網易+阿里面試真題)iOS面試程式設計師BAT阿里
- 活動抽獎元件設計元件
- 如何設計一個百萬級使用者的抽獎系統?
- 遊戲商業化設計思考(一)--抽獎遊戲
- 面試了一個 5 年 Java 程式設計師,一個問題也不會。。面試Java程式設計師
- 程式設計師程式碼面試指南程式設計師面試
- 簡單的面試總結---內含面試題目 | 掘金技術徵文面試題
- 【微信刮刮卡抽獎遊戲】教你輕鬆做個抽獎遊戲頁面遊戲
- 題目:設計一個類,我們只能生成該類的一個例項。 程式碼如下
- 程式設計師面試因抽菸被拒,網友:沒毛病程式設計師面試
- 好程式設計師Java分享Javamain十個面試題程式設計師JavaAI面試題
- 抽獎測試點
- 面試題抽答(補充)面試題
- 用vuejs擼一個抽獎元件VueJS元件
- 九宮格抽獎–手擼程式碼
- 程式設計題目解析程式設計
- 一些有趣的程式設計師智力面試題程式設計師面試題
- 面試題:如何設計一個高併發系統?面試題
- 程式設計師程式碼面試指南:IT名企演算法與資料結構題目最優解程式設計師面試演算法資料結構
- 一道騷面試題目面試題
- 2018年4月份,阿里最新的java程式設計師面試題目阿里Java程式設計師面試題
- 程式設計師50+Java面試題程式設計師Java面試題
- 智力題(程式設計師面試經典)程式設計師面試
- Java程式設計師面試題及解答Java程式設計師面試題
- JAVA程式設計師面試題庫分享Java程式設計師面試題
- 程式設計師面試題分享,掌握八成便可BAT一試!程式設計師面試題BAT
- TX 面試題目面試題
- 5年程式設計師面試,常見面試問題解析程式設計師面試
- 一個ios程式設計師年後找工作經歷(附上一些面試問題)iOS程式設計師面試
- 面試題:web程式設計技術考試題庫(含答案)面試題Web程式設計
- 【Java面試】Java設計模式面試題!Java設計模式面試題
- 【200人面試經驗】,程式設計師面試,常見面試題解析程式設計師面試題
- 面試題目:手寫一個LRU演算法實現面試題演算法
- 每個人都應該瞭解的金融小知識 — 利率計算 (含一道碼農面試題)面試題
- Linux網路程式設計常見面試題Linux程式設計面試題