package Dec2016;
import java.util.ArrayList;
import java.util.List;
public class Ques1214 {
public static void main(String[] args) {
final List tickets = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
tickets.add("高鐵票-" + i);
}
// 10 個線程窗口賣票
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getId() + "-" + tickets.remove(0));
}
}).start();
}
}
}
問題:程序輸出結果是什麼?能否實現各窗口賣的票不衝突?
依次輸出:1-高鐵票-0, 1-高鐵票-1 ... 1-高鐵票-999
size() > 0 隱性檢查 (while(true) 無顯式檢查),同時 remove(0) 導致下標越界ArrayList 非線程安全,內部 elementData 數組、size 字段無同步保護// ArrayList.remove(int index) 簡化源碼
public E remove(int index) {
rangeCheck(index); // 1. 檢查下標
modCount++; // 2. 修改計數
E oldValue = elementData[index]; // 3. 獲取值
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index, numMoved); // 4. 數組移位
elementData[--size] = null; // 5. 清理末尾、更新 size
return oldValue;
}
// 多線程並發執行上述步驟 → 數據錯亂、size 不一致、數組越界
List tickets = Collections.synchronizedList(new ArrayList<>());
// 使用時必須手動同步迭代/複合操作
synchronized (tickets) {
if (!tickets.isEmpty()) {
System.out.println(Thread.currentThread().getId() + "-" + tickets.remove(0));
}
}
// 缺點:鎖粒度粗、擴容時複製數組開銷大、迭代需手動同步
List tickets = new CopyOnWriteArrayList<>();
// 寫入時複製底層數組,讀取無鎖
// 缺點:寫入開銷極大 (複製數組)、僅保證最終一致性 (可能讀到舊數據)、內存佔用雙倍
final Object lock = new Object();
// 或 final Lock lock = new ReentrantLock();
new Thread(() -> {
while (true) {
String ticket;
synchronized (lock) { // 或 lock.lock()/unlock()
if (tickets.isEmpty()) break;
ticket = tickets.remove(0);
}
if (ticket != null) System.out.println(Thread.currentThread().getId() + "-" + ticket);
}
}).start();
// 優點:精確控制臨界區、性能優於方案1
// 缺點:代碼侵入性強、易死鎖、需自行維護鎖對象
// 初始化有界隊列,預裝票據
BlockingQueue ticketQueue = new LinkedBlockingQueue<>(1000);
for (int i = 0; i < 1000; i++) {
ticketQueue.offer("高鐵票-" + i);
}
// 消費者線程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
while (true) {
String ticket = ticketQueue.take(); // 隻要有票就取走,無票阻塞等待
System.out.println(Thread.currentThread().getId() + "-" + ticket);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
// 優點:線程安全、內置阻塞等待、無需額外同步、天然支持生產消費模型
// 缺點:需預先裝入隊列、動態補票需額外邏輯
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 簡單任務、低併發、快速原型 | 方案 1 synchronizedList | 代碼改動最小 |
| 讀多寫少、可容忍弱一致性 | 方案 2 CopyOnWriteArrayList | 讀無鎖、遍歷安全 |
| 需精確控制、避免長時間持鎖 | 方案 3 手動鎖 | 靈活、性能可控 |
| 生產消費模型、高併發、需阻塞等待 | 方案 4 BlockingQueue ⭐ | 線程安全、解耦、模型天然匹配 |
// 使用 LinkedBlockingQueue 無界版本 + 獨立補票線程
BlockingQueue ticketQueue = new LinkedBlockingQueue<>();
// 補票線程 (模擬後台系統推票)
new Thread(() -> {
int ticketId = 1000;
while (!Thread.currentThread().isInterrupted()) {
try {
ticketQueue.put("高鐵票-" + ticketId++); // 隊列滿會阻塞 (無界不會滿)
Thread.sleep(100); // 控制補票速率
} catch (InterruptedException e) { break; }
}
}).start();
ArrayList 絕對不能 在多線程環境下直接用於複合操作 (檢查-然後執行)synchronized、Lock、CopyOnWrite、BlockingQueue 的適用邊界,是併發編程從「能跑」到「穩健」的關鍵