Java多线程音乐节入场统计:定时统计与同步集合数据丢失问题咨询
问题原因分析
问题1:统计线程仅生成一次结果的原因
- 启动时机错误:现有代码中统计线程是在所有入场线程全部执行完毕(循环内逐个join)后才调用
start()启动,自然只能拿到最终全量数据做一次统计 - 统计线程无循环逻辑:
FestivalStatisticsThread的run()方法仅执行一次统计+5秒休眠就直接结束线程,没有重复执行统计的逻辑
问题2:不调用join就丢失票种数据的原因
- 现有
join()写在入场线程启动的循环内,相当于每个入场线程串行执行,所有入场执行完才会继续走main线程后续逻辑。如果去掉这里的join,main线程会在瞬间启动完8万多个入场线程后,直接执行完剩余逻辑退出,JVM会在所有非守护线程结束后终止,大量还没来得及执行add()操作的入场线程会被直接销毁,自然数据不全。 - 额外说明:
FestivalAttendeeThread的run()方法加的synchronized完全无效,该关键字锁的是当前线程实例,每个入场线程锁自己的对象,不会起到任何互斥作用,属于冗余代码。
修复方案
第一步:调整统计线程逻辑
- 给
run()方法添加循环逻辑,支持每5秒重复统计 - 每次统计前先清空计数变量,避免历史数据累加
- 新增中断退出逻辑,方便所有入场结束后终止统计线程
修改后的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方法执行流程
- 提前启动统计线程,保证入场过程中就能定时输出统计结果
- 把所有入场线程先存入列表,全部启动后再统一join,既保证多线程并行执行,又能等待所有入场完成
- 所有入场结束后给统计线程发中断信号,输出最终结果后退出
修改后的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
相关产品推荐
相关产品推荐

