首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程模擬多站點售票系統實戰

     編輯:Java 相關知識

專案背景與需求分析

在高併發場景下,多線程對共享資源的訪問必須保證線程安全。本文通過模擬火車票售票系統,展示如何使用 Java 多線程技術實現多站點、多售票窗口的並發售票場景。系統包含三個目的地:北京、上海、天津,每個目的地初始票數 5 張,啟動 5 個售票窗口(線程),每個窗口處理 5 筆交易。

核心類設計

1. 目的地枚舉

package com.zj.tickets;

public enum Destination {
    BEIJING, SHANGHAI, TIANJIN
}

2. 車票實體類

採用不可變模式設計,確保線程安全:

package com.zj.tickets;

public class Ticket {
    private final int original;      // 初始票數
    private int current;             // 當前剩餘票數
    private final Destination destination;

    public Ticket(int nums, Destination dest) {
        this.original = nums;
        this.current = nums;
        this.destination = dest;
    }

    // 售出一張票,返回剩餘票數
    public synchronized int sellOne() {
        return --current;
    }

    public boolean isSoldOut() {
        return current <= 0;
    }

    public int getCurrent() { return current; }
    public int getOriginal() { return original; }
    public Destination getDestination() { return destination; }
}

3. 售票窗口類

實現 Runnable 接口,每個窗口獨立運行:

package com.zj.tickets;

import java.util.*;
import java.util.concurrent.TimeUnit;

public class BookingOffice implements Runnable {
    // 共享票池,所有窗口共用
    private static final Map<Destination, Ticket> ticketPool = new HashMap<>();
    private static final List<BookingOffice> offices = new ArrayList<>();
    
    private final Map<Destination, Integer> salesRecord = new HashMap<>();
    private int ticketsSold = 0;
    private final int officeId;

    static {
        ticketPool.put(Destination.BEIJING, new Ticket(5, Destination.BEIJING));
        ticketPool.put(Destination.SHANGHAI, new Ticket(5, Destination.SHANGHAI));
        ticketPool.put(Destination.TIANJIN, new Ticket(5, Destination.TIANJIN));
    }

    public BookingOffice(int id) {
        this.officeId = id;
        offices.add(this);
    }

    @Override
    public void run() {
        int transactions = 5;
        Random random = new Random();
        
        while (transactions-- > 0) {
            // 模擬客戶隨機選擇目的地
            Destination dest = Destination.values()[random.nextInt(Destination.values().length)];
            log("客戶請求購買 " + dest + " 車票");

            // 模擬查詢延遲
            try { TimeUnit.SECONDS.sleep(1); } 
            catch (InterruptedException e) { Thread.currentThread().interrupt(); }

            // 同步售票邏輯
            Ticket ticket = ticketPool.get(dest);
            synchronized (ticket) {
                if (!ticket.isSoldOut()) {
                    int remaining = ticket.sellOne();
                    ticketsSold++;
                    salesRecord.merge(dest, 1, Integer::sum);
                    log("成功售出 " + dest + " 車票,剩餘:" + remaining);
                } else {
                    log(dest + " 車票已售罄");
                }
            }
        }
        log("窗口關閉 | 總售出:" + ticketsSold + " | 明細:" + salesRecord);
    }

    private void log(String msg) {
        System.out.println("<Office-" + officeId + "> " + msg);
    }
}

4. 主程序啟動類

package com.zj.tickets;

import java.util.concurrent.*;

public class TicketSystem {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executor.submit(new BookingOffice(i));
        }
        executor.shutdown();
    }
}

運行結果分析

程序輸出展示了多線程並發訪問共享資源時的互斥效果:

  • 每個窗口獨立處理請求,但對同一目的地車票的操作被正確同步
  • 總計 15 張車票(3地 × 5張)最終全部售出
  • 各窗口銷售記錄清晰可追蹤,無超賣現象

關鍵技術點總結

  1. 共享資源保護:使用 synchronized(ticket) 針對具體車票對象加鎖,粒度細、效率高
  2. 線程池管理:採用 ExecutorService 統一管理線程生命周期
  3. 不可變設計:目的地枚舉、初始票數等採用 final 修飾,減少同步需求
  4. 原子操作:售票邏輯在同步塊內完成查詢與扣減,保證原子性

擴展建議

實際生產環境可考慮:

  • 使用 ReentrantLock 替代 synchronized 實現可中斷、可超時的鎖獲取
  • 引入 AtomicInteger 優化計數器性能
  • 增加數據庫持久化層,採用樂觀鎖(版本號)處理分佈式場景
  • 實現票務查詢緩存,降低數據庫壓力
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved