【实践】练习抢红包逻辑

以下实现仅个人练习,仅供参考,不足之处请多多留言指教,感激不尽。

分析

抢红包有以下信息需要考虑:

  • 红包总额:一般固定
  • 红包数量:固定或不定
  • 抢红包人数:不定
  • 每个红包大小:固定或随机
    • 抢红包策略:公平或随机

公平抢红包

基本逻辑实现:

public class GradCommon {
    private int redPacket;
    private int redPacketNumber;
    private final int redPacketSize;
    private final int lastRedPacketSize;

    public GradCommon(int redPacket,int redPacketNumber) {
        this.redPacketNumber = redPacketNumber;
        this.redPacketSize = redPacket/redPacketNumber;
        this.lastRedPacketSize = redPacketSize + redPacket % redPacketNumber;
    }

    public int grad(){
        int currentRedPacket = 0;
        if (redPacketNumber>0){
            redPacketNumber--;
            currentRedPacket = redPacketSize;
            if (redPacketNumber == 1){
                currentRedPacket = lastRedPacketSize;
            }
            System.out.println(Thread.currentThread().getName() + " 抢到红包" + currentRedPacket + "分,奖励一下!");
        }else {
            System.out.println(Thread.currentThread().getName() + " 未抢到,感觉错过了一个亿!");
        }
        return currentRedPacket;
    }
}
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入红包总金额(分):");
        int redPacket = scanner.nextInt();
        System.out.println("请输入红包总数:");
        int redPacketNumber = scanner.nextInt();
        System.out.println("请输入抢红包人数");
        int people = scanner.nextInt();

        GradCommon grad = new GradCommon(redPacket,redPacketNumber);
        for (int i = 0; i < people; i++) {
            grad.grad(); // 注意此位置问题
        }
    }

以上代码逻辑问题:

  • 不能实现同时开抢,只能排队抢

改进1.0

每个人抢红包的动作作为一个独立的线程,所以以多个线程方式抢红包,并且使用CountDownLatch实现同时开抢效果:

public class GradThread implements Runnable{
    private int redPacketNumber;
    private final int redPacketSize;
    private final int lastRedPacketSize;

    private final CountDownLatch countDownLatch;

    public GradThread(int redPacket, int redPacketNumber,CountDownLatch countDownLatch) {
        this.redPacketNumber = redPacketNumber;
        this.countDownLatch = countDownLatch;
        this.redPacketSize = redPacket / redPacketNumber;
        this.lastRedPacketSize = this.redPacketSize + redPacket % redPacketNumber;
    }

    @Override
    public void run() {
        try {
            countDownLatch.await(); // 争取同时开始竞价
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if (redPacketNumber>0){
            redPacketNumber--;
            int currentRedPacket = redPacketSize;
            if (redPacketNumber == 1){
                currentRedPacket = lastRedPacketSize;
            }
            System.out.println(Thread.currentThread().getName() + " 抢到红包" + currentRedPacket + "分,奖励一下!");
        }else {
            System.out.println(Thread.currentThread().getName() + " 未抢到,感觉错过了一个亿!");
        }
    }
}
    public static void main(String[] args) {
        // ...同上
        CountDownLatch countDownLatch = new CountDownLatch(1);
        GradThread gradThread = new GradThread(redPacket,redPacketNumber,countDownLatch);
        for (int i = 0; i < people; i++) {
           new Thread(gradThread).start();
        }
        countDownLatch.countDown();// 将值降为0,代表同时启动countDownLatch.await()的后续逻辑
    }

问题:

  • 无法保证数据安全,容易造成红包超出计划。

改进2.0

增加同步锁synchronized实现抢红包代码块锁定,避免红包超出

public class GradThread implements Runnable{
    // ...其他同上
    // 增加一个对象锁
    private final Object lock = new Object();

    @Override
    public void run() {
        try {
            countDownLatch.await(); 
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        synchronized (lock){ // 加锁避免超出
            if (redPacketNumber>0){
                redPacketNumber--;
                int currentRedPacket = redPacketSize;
                if (redPacketNumber == 1){
                    currentRedPacket = lastRedPacketSize;
                }
                System.out.println(Thread.currentThread().getName() + " 抢到红包" + currentRedPacket + "分,奖励一下!");
            }else {
                System.out.println(Thread.currentThread().getName() + " 未抢到,感觉错过了一个亿!");
            }
        }
    }
}

随机红包

随机红包即每个人抢到的红包并不是固定的,是随机下发的。

public class RandomGradThread  implements Runnable
    private final Random random = new Random();
    private int redPacketNumber; // 红包数量
    private int redPacket; // 红包总额
    private int gradRedPacketNumber; // 已抢红包数量
    private int gradRedPacket;// 已抢红包总额
    private final int minRedPacket = 1; // 在红包数量有效的情况下,每个红包最少有一个,不能为0
    private final CountDownLatch countDownLatch;

    public RandomGradRedPacket(int redPacket,int redPacketNumber,CountDownLatch countDownLatch) {
        this.redPacket = redPacket;
        this.redPacketNumber = redPacketNumber;
        this.countDownLatch = countDownLatch;
        System.out.println("。。。随机红包逻辑。。。");
    }
    @Override
    public void run() {
        try {
            countDownLatch.await(); // 争取同时开始竞价
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        grad();
    }

    // 抢红包逻辑
    private synchronized void grad() {
        if (redPacketNumber == 0){
            System.out.println(Thread.currentThread().getName() + " 未抢到,感觉错过了一个亿!");
            return;
        }
        int currentRedPacket = gradCurRedPacket();
//        assert currentRedPacket > 0:"红包大小不应该为0!";
        if (currentRedPacket == 0){
            throw new RuntimeException("红包大小不应该为0!");
        }
        redPacketNumber--;
        System.out.println(Thread.currentThread().getName() + " 抢到红包" + currentRedPacket + "分,奖励一下!");
        grad(currentRedPacket);
    }
    // 记录已抢红包情况
    private void grad(int gradRedPacket){
        gradRedPacketNumber++;
        this.gradRedPacket += gradRedPacket;
        System.out.println("已抢红包"+gradRedPacketNumber + "个,已抢红包" + gradRedPacket + "分,剩余红包" + (redPacket - this.gradRedPacket) + "分。");
    }
    // 计算应抢红包额度
    private int gradCurRedPacket() {
        int residue = this.redPacket - this.gradRedPacket;
        if (redPacketNumber == 1) return residue;
        int currentRedPacket = 0;
        while (currentRedPacket == 0){
            currentRedPacket = random.nextInt(residue - (redPacketNumber - 1) * minRedPacket);
        }
        return currentRedPacket;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入红包总金额(分):");
        int redPacket = scanner.nextInt();
        System.out.println("请输入红包总数:");
        int redPacketNumber = scanner.nextInt();
        System.out.println("请输入抢红包人数");
        int people = scanner.nextInt();
        
        CountDownLatch countDownLatch = new CountDownLatch(1);
        RandomGradThread grad = new RandomGradThread(redPacket,redPacketNumber,countDownLatch);
        for (int i = 0; i < people; i++) {
            new Thread(grad).start; // 注意此位置问题
        }
        countDownLatch.countDown();
    }

整合版

即抽出统一的抢红包逻辑,根据不同逻辑定义不同实现:


public class RedPacketThread implements Runnable{

    private final CountDownLatch countDownLatch;
    private final AbstractGradRedPacket redPacket;

    public RedPacketThread(CountDownLatch countDownLatch, AbstractGradRedPacket redPacket) {
        this.countDownLatch = countDownLatch;
        this.redPacket = redPacket;
    }

    @Override
    public void run() {
        try {
            countDownLatch.await(); // 争取同时开始竞价
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        redPacket.grad();
    }
}
/**
 * 抢红包抽象类
 */
public abstract class AbstractGradRedPacket {
    protected int redPacket;
    protected int gradRedPacketNumber;
    protected int gradRedPacket;

    public AbstractGradRedPacket(int redPacket) {
        this.redPacket = redPacket;
    }

    abstract void grad();

    // 收集已抢红包情况
    public synchronized void grad(int gradRedPacket){
        gradRedPacketNumber++;
        this.gradRedPacket += gradRedPacket;
        System.out.println("已抢红包"+gradRedPacketNumber + "个,已抢红包" + gradRedPacket + "分,剩余红包" + (redPacket - this.gradRedPacket) + "分。");
    }
}

/**
 * 固定红包总额和数量的抢红包抽象逻辑
 */
public abstract class FixRedPacket extends AbstractGradRedPacket{

    protected int redPacketNumber;

    public FixRedPacket(int redPacket,int redPacketNumber) {
        super(redPacket);
        this.redPacketNumber = redPacketNumber;
    }

    @Override
    synchronized void grad() {
        if (redPacketNumber == 0){
            System.out.println(Thread.currentThread().getName() + " 未抢到,感觉错过了一个亿!");
            return;
        }
        int currentRedPacket = gradCurRedPacket();
//        assert currentRedPacket > 0:"红包大小不应该为0!";
        if (currentRedPacket == 0){
            throw new RuntimeException("红包大小不应该为0!");
        }
        redPacketNumber--;
        System.out.println(Thread.currentThread().getName() + " 抢到红包" + currentRedPacket + "分,奖励一下!");
        grad(currentRedPacket);
    }

    abstract int gradCurRedPacket();
}

/**
 * 固定红包
 */
public class FailGradRedPacket extends FixRedPacket{

    private final int redPacketSize;
    private final int lastRedPacketSize;

    public FailGradRedPacket(int redPacket, int redPacketNumber) {
        super(redPacket,redPacketNumber);
        this.redPacketSize = redPacket / redPacketNumber;
        this.lastRedPacketSize = this.redPacketSize + redPacket % redPacketNumber;
        System.out.println("。。。公平红包逻辑。。。");
    }

    @Override
    int gradCurRedPacket() {
        int currentRedPacket = redPacketSize;
        if (redPacketNumber == 1){
            currentRedPacket = lastRedPacketSize;
        }
        return currentRedPacket;
    }
}

/**
 * 随机红包
 */
public class RandomGradRedPacket extends FixRedPacket{

    private final Random random = new Random();
    private final int minRedPacket = 1;

    public RandomGradRedPacket(int redPacket,int redPacketNumber) {
        super(redPacket,redPacketNumber);
        System.out.println("。。。随机红包逻辑。。。");
    }

    @Override
    synchronized int gradCurRedPacket() {
        int residue = this.redPacket - this.gradRedPacket;
        if (redPacketNumber == 1) return residue;
        int currentRedPacket = 0;
        while (currentRedPacket == 0){
            currentRedPacket = random.nextInt(residue - (redPacketNumber - 1) * minRedPacket);
        }
        return currentRedPacket;
    }

    private int gradRedPacket(int residue){
        int currentRedPacket = 0;
        while (currentRedPacket == 0){
            currentRedPacket = random.nextInt(residue - (redPacketNumber - 1) * minRedPacket);
        }
        return currentRedPacket;
    }
}
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入红包总金额(分):");
        int redPacket = scanner.nextInt();
        System.out.println("请输入红包总数:");
        int redPacketNumber = scanner.nextInt();
        System.out.println("请输入抢红包人数");
        int people = scanner.nextInt();
        CountDownLatch countDownLatch = new CountDownLatch(1);
        AbstractGradRedPacket gradRedPacket;
//        gradRedPacket = new FailGradRedPacket(redPacket,redPacketNumber);
        gradRedPacket = new RandomGradRedPacket(redPacket,redPacketNumber);
        RedPacketThread redPacketThread = new RedPacketThread(countDownLatch,gradRedPacket);
        for (int i = 0; i < people; i++) {
            new Thread(redPacketThread).start();
        }
        countDownLatch.countDown();
    }

测试

请输入红包总金额(分):
100
请输入红包总数:
10
请输入抢红包人数
12
。。。随机红包逻辑。。。
Thread-3 抢到红包38分,奖励一下!
已抢红包1个,已抢红包38分,剩余红包62分。
Thread-7 抢到红包42分,奖励一下!
已抢红包2个,已抢红包42分,剩余红包20分。
Thread-4 抢到红包7分,奖励一下!
已抢红包3个,已抢红包7分,剩余红包13分。
Thread-5 抢到红包1分,奖励一下!
已抢红包4个,已抢红包1分,剩余红包12分。
Thread-8 抢到红包1分,奖励一下!
已抢红包5个,已抢红包1分,剩余红包11分。
Thread-6 抢到红包6分,奖励一下!
已抢红包6个,已抢红包6分,剩余红包5分。
Thread-2 抢到红包1分,奖励一下!
已抢红包7个,已抢红包1分,剩余红包4分。
Thread-9 抢到红包1分,奖励一下!
已抢红包8个,已抢红包1分,剩余红包3分。
Thread-1 抢到红包1分,奖励一下!
已抢红包9个,已抢红包1分,剩余红包2分。
Thread-10 抢到红包2分,奖励一下!
已抢红包10个,已抢红包2分,剩余红包0分。
Thread-11 未抢到,感觉错过了一个亿!
Thread-0 未抢到,感觉错过了一个亿!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值