在高併發場景下,多線程對共享資源的訪問必須保證線程安全。本文通過模擬火車票售票系統,展示如何使用 Java 多線程技術實現多站點、多售票窗口的並發售票場景。系統包含三個目的地:北京、上海、天津,每個目的地初始票數 5 張,啟動 5 個售票窗口(線程),每個窗口處理 5 筆交易。
package com.zj.tickets;
public enum Destination {
BEIJING, SHANGHAI, TIANJIN
}
採用不可變模式設計,確保線程安全:
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; }
}
實現 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);
}
}
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();
}
}
程序輸出展示了多線程並發訪問共享資源時的互斥效果:
synchronized(ticket) 針對具體車票對象加鎖,粒度細、效率高ExecutorService 統一管理線程生命周期實際生產環境可考慮:
ReentrantLock 替代 synchronized 實現可中斷、可超時的鎖獲取AtomicInteger 優化計數器性能