You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java多线程音乐节入场统计:定时统计与同步集合数据丢失问题咨询

问题原因分析

问题1:统计线程仅生成一次结果的原因

  • 启动时机错误:现有代码中统计线程是在所有入场线程全部执行完毕(循环内逐个join)后才调用start()启动,自然只能拿到最终全量数据做一次统计
  • 统计线程无循环逻辑:FestivalStatisticsThread的run()方法仅执行一次统计+5秒休眠就直接结束线程,没有重复执行统计的逻辑

问题2:不调用join就丢失票种数据的原因

  • 现有join()写在入场线程启动的循环内,相当于每个入场线程串行执行,所有入场执行完才会继续走main线程后续逻辑。如果去掉这里的join,main线程会在瞬间启动完8万多个入场线程后,直接执行完剩余逻辑退出,JVM会在所有非守护线程结束后终止,大量还没来得及执行add()操作的入场线程会被直接销毁,自然数据不全。
  • 额外说明:FestivalAttendeeThread的run()方法加的synchronized完全无效,该关键字锁的是当前线程实例,每个入场线程锁自己的对象,不会起到任何互斥作用,属于冗余代码。
修复方案

第一步:调整统计线程逻辑

  1. 给run()方法添加循环逻辑,支持每5秒重复统计
  2. 每次统计前先清空计数变量,避免历史数据累加
  3. 新增中断退出逻辑,方便所有入场结束后终止统计线程
    修改后的FestivalStatisticsThread核心代码:
@Override
public void run() {
    // 循环执行统计,收到中断信号后退出
    while (!Thread.currentThread().isInterrupted()) {
        // 每次统计前先清零所有计数
        NR_OF_FULL = 0;
        NR_OF_FULL_VIP = 0;
        NR_OF_FREE_PASS = 0;
        NR_OF_ONE_DAY = 0;
        NR_OF_ONE_DAY_VIP = 0;
        TOTAL_NUMBER_OF_PEOPLE = 0;
        
        calculateNumberOfEachTicketType();
        calculateNumberOfPeople();
        showStats();
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // 收到中断信号,退出循环
            break;
        }
    }
    // 退出前输出最后一次全量统计
    NR_OF_FULL = 0;
    NR_OF_FULL_VIP = 0;
    NR_OF_FREE_PASS = 0;
    NR_OF_ONE_DAY = 0;
    NR_OF_ONE_DAY_VIP = 0;
    TOTAL_NUMBER_OF_PEOPLE = 0;
    calculateNumberOfEachTicketType();
    calculateNumberOfPeople();
    System.out.println("===== 入场全部结束,最终统计结果 =====");
    showStats();
}

第二步:调整Main方法执行流程

  1. 提前启动统计线程,保证入场过程中就能定时输出统计结果
  2. 把所有入场线程先存入列表,全部启动后再统一join,既保证多线程并行执行,又能等待所有入场完成
  3. 所有入场结束后给统计线程发中断信号,输出最终结果后退出
    修改后的Main代码:
public class Main {
    public static void main(String[] args) throws InterruptedException {
        FestivalGate gate = new FestivalGate();
        FestivalStatisticsThread statsThread = new FestivalStatisticsThread(gate);
        // 提前启动统计线程
        statsThread.start();
        
        List<FestivalAttendeeThread> attendeeList = new ArrayList<>();
        TicketType ticketType;
        for (int i = 1; i < 85_000; i++) {
            ticketType = TicketType.randomTicket();
            FestivalAttendeeThread festivalAttendee = new FestivalAttendeeThread(ticketType, gate);
            attendeeList.add(festivalAttendee);
            festivalAttendee.start();
        }
        // 等待所有入场线程全部执行完成
        for (FestivalAttendeeThread attendee : attendeeList) {
            attendee.join();
        }
        // 给统计线程发中断信号,让它输出最终结果后退出
        statsThread.interrupt();
        statsThread.join();
        
        System.out.println(gate.getValidatedTickets().size());
    }
}

第三步:删除冗余代码

去掉FestivalAttendeeThread的run()方法上的synchronized关键字,已经有同步集合保证add操作线程安全,不需要额外加锁:

@Override
public void run() {
    this.festivalGate.getValidatedTickets().add(ticketType);
}

内容的提问来源于stack exchange,提问作者Darius Boengiu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 02:45:04