【练习十二】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()**等修改操作必须放在同步代码块内,避免并发修改异常和数据错乱。
相关推荐
LONGZETECH2 小时前
新能源汽车充电设备装配与调试仿真教学软件 技术解析与教学落地
开发语言·系统架构·汽车·汽车教学软件·智能网联汽车软件
indexsunny2 小时前
互联网大厂Java求职面试实战:核心技术与业务场景解析
java·spring boot·redis·微服务·kafka·互联网大厂·面试技巧
User_芊芊君子2 小时前
2026最新Python+AI入门指南:从零基础到实战落地,避开90%新手坑
开发语言·人工智能·python
小涛不学习2 小时前
Java 后端核心框架面试题(Spring / SpringMVC / MyBatis / MyBatis-Plus)
java·spring·mybatis
im_AMBER2 小时前
Leetcode 141 最长公共前缀 | 罗马数字转整数
算法·leetcode
程序猿大波2 小时前
基于java,SpringBoot和Vue餐饮公司食堂管理系统设计
java·vue.js·spring boot
似水明俊德2 小时前
01-C#.Net-泛型-学习笔记
java·笔记·学习·c#·.net
百锦再2 小时前
飞算 JavaAI:我的编程强力助推引擎
java·spring·ai·编程·idea·code·飞算
InfiniSynapse2 小时前
连上Snowflake就能取数:InfiniSynapse + Spider2-Snow实战企业数据分析
数据结构·图像处理·人工智能·算法·语言模型·数据挖掘·数据分析