【练习十二】Java实现年会红包雨小游戏

我们用一个贴近生活的 "年会红包雨" 案例,手把手带你实现多线程抢红包逻辑 ------ 这个案例涵盖线程创建、线程安全、同步代码块等核心知识点。


目录

一、案例需求:年会红包雨规则

二、案例核心知识点

三、代码分步拆解:实现红包雨

[步骤 1:准备红包池](#步骤 1:准备红包池)

[步骤 2:定义员工抢红包线程类](#步骤 2:定义员工抢红包线程类)

[步骤 3:启动 100 个员工线程](#步骤 3:启动 100 个员工线程)

四、完整源码

包结构

Test.java

PeopleGetRedPacket.java

五、运行结果演示

六、核心知识点总结


一、案例需求:年会红包雨规则

某公司年会搞红包雨活动,规则对应多线程核心场景:

  1. 参与对象:100 名员工(对应 100 个独立线程);
  2. 红包总数:200 个;
  3. 红包金额规则:
    • 小红包(1-30 元):占比 80%(共 160 个);
    • 大红包(31-100 元):占比 20%(共 40 个);
  4. 核心逻辑:100 个线程同时抢 200 个红包,保证每个红包只能被抢一次,抢完即止。

二、案例核心知识点

知识点 作用 新手提示
继承 Thread 类创建线程 每个员工对应一个独立线程,实现并发抢红包 启动线程必须调用start(),不能直接调run()
同步代码块(synchronized) 解决多线程抢红包的 "数据错乱" 问题 锁对象必须是多个线程共享的对象
ArrayList 集合 存储红包列表,通过remove()实现 "抢红包" ArrayList 本身非线程安全,需加锁保护
Thread.sleep() 模拟抢红包的延迟,让执行过程更易观察 休眠时不会释放锁,仅暂停线程执行

三、代码分步拆解:实现红包雨

我们把案例拆成 3 个核心步骤,逐行讲解,确保新手能看懂每一步的逻辑~

步骤 1:准备红包池

首先需要生成 200 个红包,按 80%/20% 的比例分配小红包和大红包,存入 List 作为 "红包池"。

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Test {
    public static void main(String[] args) {
        // 1. 生成200个红包,存入列表(红包池)
        List<Integer> redPacketList = getRedPackets();
        
        // 3. 创建100个员工线程,启动抢红包(步骤2先定义线程类)
        for (int i = 1; i <= 100; i++) {
            new PeopleGetRedPacket(redPacketList,"员工"+ i).start();
        }
    }

    // 核心方法:生成200个红包
    private static List<Integer> getRedPackets() {
        Random random = new Random();
        List<Integer> redPacketList = new ArrayList<>();
        
        // 小红包:1-30元,160个(80%)
        for (int i = 0; i < 160; i++) {
            // random.nextInt(30)生成0-29,+1后是1-30
            redPacketList.add(random.nextInt(30) + 1);
        }
        
        // 大红包:31-100元,40个(20%)
        for (int i = 0; i < 40; i++) {
            // random.nextInt(70)生成0-69,+31后是31-100
            redPacketList.add(random.nextInt(70) + 31);
        }
        return redPacketList;
    }
}

关键解释

  • Random.nextInt(n):生成0 ~ n-1的随机整数,通过+1调整金额范围;
  • ArrayList 是 "红包池" 的载体,支持随机索引获取和删除元素,契合 "抢红包" 的逻辑。

步骤 2:定义员工抢红包线程类

每个员工对应一个线程,需要继承 Thread 类,重写**run()**方法实现抢红包逻辑 ------核心是解决线程安全问题(100 个线程同时操作同一个红包池,不加锁会出现 "重复抢红包""红包数量负数" 等问题)。

java 复制代码
import java.util.List;

// 员工抢红包线程类:继承Thread
public class PeopleGetRedPacket extends Thread{
    // 共享资源:所有线程共用同一个红包池
    private List<Integer> redPackets;

    // 构造器:传入红包池 + 设置线程名称(员工编号)
    public PeopleGetRedPacket(List<Integer> redPackets, String name){
        super(name); // 调用父类构造器,设置线程名
        this.redPackets = redPackets;
    }

    @Override
    public void run() {
        String userName = Thread.currentThread().getName(); // 获取当前员工名称
        while (true) {
            // 同步代码块:加锁保护共享资源(红包池)
            // 锁对象必须是所有线程共享的(这里用红包池),否则锁无效
            synchronized (redPackets) {
                // 终止条件:红包池为空,结束抢红包
                if (redPackets.size() == 0) {
                    break;
                }

                // 随机获取一个红包索引(模拟"抢"的随机性)
                int index = (int)(Math.random() * redPackets.size());
                Integer money = redPackets.remove(index); // 删除红包=抢到红包
                System.out.println(userName + "抢到红包:" + money + "元");

                // 最后一个红包抢完,提示活动结束
                if (redPackets.size() == 0) {
                    System.out.println("\n🎉 活动结束!所有红包已抢完~");
                    break;
                }
            }

            // 模拟抢红包的延迟(比如点击、网络延迟),便于观察结果
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

核心重点(线程安全)

  • synchronized (redPackets):给红包池加锁,确保同一时间只有 1 个线程能执行抢红包代码,避免 "多个线程同时删同一个红包";
  • redPackets.remove(index):删除对应索引的红包,代表 "抢到"------ 这是操作共享资源的核心代码,必须放在同步代码块内;
  • Thread.sleep(10):让线程休眠 10 毫秒,模拟真实抢红包的延迟,否则线程执行太快,结果一闪而过。

步骤 3:启动 100 个员工线程

回到 Test 类的 main 方法,循环创建 100 个员工线程(命名为 "员工 1" 到 "员工 100"),调用**start()**启动线程,抢红包逻辑就会并发执行。


四、完整源码

包结构

java 复制代码
ThreadTest/
├─ Test.java       // 主线程:生成红包+启动抢红包线程
└─ PeopleGetRedPacket.java  // 员工抢红包线程类

Test.java

java 复制代码
package ThreadTest;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Test {
    public static void main(String[] args) {
        // 1. 生成200个红包,存入红包池
        List<Integer> redPacketList = getRedPackets();
        
        // 2. 创建100个员工线程,启动抢红包
        for (int i = 1; i <= 100; i++) {
            new PeopleGetRedPacket(redPacketList,"员工"+ i).start();
        }
    }

    // 生成200个红包:160个小红包(1-30) + 40个大红包(31-100)
    private static List<Integer> getRedPackets() {
        Random random = new Random();
        List<Integer> redPacketList = new ArrayList<>();
        
        // 小红包:80%占比
        for (int i = 0; i < 160; i++) {
            redPacketList.add(random.nextInt(30) + 1);
        }
        // 大红包:20%占比
        for (int i = 0; i < 40; i++) {
            redPacketList.add(random.nextInt(70) + 31);
        }
        return redPacketList;
    }
}

PeopleGetRedPacket.java

java 复制代码
package ThreadTest;

import java.util.List;

public class PeopleGetRedPacket extends Thread{
    private List<Integer> redPackets;

    public PeopleGetRedPacket(List<Integer> redPackets, String name){
        super(name);
        this.redPackets = redPackets;
    }

    @Override
    public void run() {
        String userName = Thread.currentThread().getName();
        while (true){
            // 同步代码块:保护共享资源,解决线程安全问题
            synchronized (redPackets) {
                if (redPackets.size() == 0){
                    break;
                }
                // 随机抢一个红包
                int index = (int)(Math.random()*redPackets.size());
                Integer money = redPackets.remove(index);
                System.out.println(userName + "抢到红包:" + money + "元");
                
                if (redPackets.size() == 0) {
                    System.out.println("\n🎉 活动结束!所有红包已抢完~");
                    break;
                }
            }
            // 模拟延迟,让抢红包过程更真实
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

五、运行结果演示

运行 Test 类后,控制台会输出类似内容(多线程执行顺序随机,每次结果不同):

java 复制代码
员工1抢到红包:15元
员工3抢到红包:88元
员工2抢到红包:22元
员工5抢到红包:5元
员工4抢到红包:99元
...
员工99抢到红包:28元
员工100抢到红包:76元

🎉 活动结束!所有红包已抢完~

六、核心知识点总结

  1. 多线程创建 :继承 Thread 类创建线程,每个线程对应一个员工,调用**start()启动(直接调run()**只是普通方法,不会并发执行);
  2. 线程安全 :多线程操作共享资源(红包池)时,必须用synchronized加锁,锁对象要保证所有线程共用(本例用红包池 List);
  3. 共享资源操作 :ArrayList 非线程安全,其**remove()**等修改操作必须放在同步代码块内,避免并发修改异常和数据错乱。
相关推荐
NHuan^_^16 小时前
SpringBoot3 整合 SpringAI 实现ai助手(记忆)
java·人工智能·spring boot
蚂蚁数据AntData16 小时前
破解AI“机器味“困境:HeartBench评测实践详解
大数据·人工智能·算法·机器学习·语言模型·开源
ZC跨境爬虫16 小时前
Python异步IO详解:原理、应用场景与实战指南(高并发爬虫首选)
爬虫·python·算法·自动化
前进的李工17 小时前
MySQL大小写规则与存储引擎详解
开发语言·数据库·sql·mysql·存储引擎
Mr_Xuhhh17 小时前
从ArrayList到LinkedList:理解链表,掌握Java集合的另一种选择
java·数据结构·链表
倦王17 小时前
力扣日刷47-补
python·算法·leetcode
错把套路当深情17 小时前
Java 全方向开发技术栈指南
java·开发语言
前端郭德纲17 小时前
JavaScript Object.freeze() 详解
开发语言·javascript·ecmascript
han_hanker17 小时前
springboot 一个请求的顺序解释
java·spring boot·后端
MaCa .BaKa17 小时前
44-校园二手交易系统(小程序)
java·spring boot·mysql·小程序·maven·intellij-idea·mybatis