public final class FastRandom {
private int seed;
public FastRandom(int seed) { this.seed = seed; }
// XORShift 算法:無狀態、極快、統計學特性良好
public int nextInt() {
seed ^= (seed << 6);
seed ^= (seed >>> 21);
seed ^= (seed << 7);
return seed;
}
// 工廠方法:以納秒時間 + hashCode 作為種子,減少相關性
public static FastRandom newInstance() {
return new FastRandom((int) System.nanoTime() ^ Thread.currentThread().hashCode());
}
}
優於 java.util.Random(CAS 爭用)與 ThreadLocalRandom(Java 7+ 內建,實際生產首選)。
public class BoundedBuffer<E> {
private final Semaphore availableItems, availableSpaces;
private final E[] items;
private int putPos = 0, takePos = 0;
@SuppressWarnings("unchecked")
public BoundedBuffer(int capacity) {
availableItems = new Semaphore(0);
availableSpaces = new Semaphore(capacity);
items = (E[]) new Object[capacity];
}
public void put(E x) throws InterruptedException {
availableSpaces.acquire(); // 等待空槽
synchronized (this) { // 保護環形數組寫入
items[putPos] = x;
putPos = (++putPos == items.length) ? 0 : putPos;
}
availableItems.release(); // 通知有數據
}
public E take() throws InterruptedException {
availableItems.acquire(); // 等待數據
E x;
synchronized (this) { // 保護環形數組讀取
x = items[takePos];
items[takePos] = null; // 幫助 GC
takePos = (++takePos == items.length) ? 0 : takePos;
}
availableSpaces.release(); // 通知有空槽
return x;
}
public boolean isEmpty() { return availableItems.availablePermits() == 0; }
public boolean isFull() { return availableSpaces.availablePermits() == 0; }
}
設計亮點:雙信號量分別控制「空槽」與「數據項」,環形數組 + 細粒度同步塊,吞吐量遠高於 synchronized 整體鎖。
ExecutorService:線程池(newCachedThreadPool)CyclicBarrier:雙層柵欄 —— AtomicInteger:無鎖累加驗證總和
public class PutTakeTest {
private static final ExecutorService POOL = Executors.newCachedThreadPool();
private final AtomicInteger putSum = new AtomicInteger(0);
private final AtomicInteger takeSum = new AtomicInteger(0);
private final CyclicBarrier barrier;
private final BoundedBuffer<Integer> buffer;
private final int trials, pairs;
public PutTakeTest(int capacity, int pairs, int trials) {
this.buffer = new BoundedBuffer<>(capacity);
this.trials = trials;
this.pairs = pairs;
// 2*pairs 個工作線程 + 1 個主線程 = 2*pairs+1
this.barrier = new CyclicBarrier(2 * pairs + 1);
}
// 生產者
class Producer implements Runnable {
@Override public void run() {
try {
int seed = FastRandom.newInstance().nextInt();
int sum = 0;
barrier.await(); // 等待統一起跑
for (int i = 0; i < trials; i++) {
buffer.put(seed);
sum += seed;
seed = FastRandom.newInstance().nextInt(); // 簡化:實際應用 XORShift 迭代
}
putSum.addAndGet(sum);
barrier.await(); // 等待全部結束
} catch (Exception e) { throw new RuntimeException(e); }
}
}
// 消費者
class Consumer implements Runnable {
@Override public void run() {
try {
barrier.await();
int sum = 0;
for (int i = 0; i < trials; i++) {
sum += buffer.take();
}
takeSum.addAndGet(sum);
barrier.await();
} catch (Exception e) { throw new RuntimeException(e); }
}
}
public void test() {
try {
for (int i = 0; i < pairs; i++) {
POOL.execute(new Producer());
POOL.execute(new Consumer());
}
barrier.await(); // 主線程釋放起跑柵欄
barrier.await(); // 主線程等待結束柵欄
if (putSum.get() == takeSum.get()) {
System.out.println("✅ 測試通過:putSum == takeSum,併發邏輯正確");
} else {
System.err.println("❌ 測試失敗:putSum=" + putSum.get() + " != takeSum=" + takeSum.get());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
new PutTakeTest(10, 2, 100_000).test();
POOL.shutdown();
}
}
| 技巧 | 目的 |
|---|---|
| CyclicBarrier 雙層同步 | 消除「啟動時間差」帶來的偽併發,確保真正並行壓測 |
| 原子累加驗證和 | 無鎖統計生產/消費總和,若相等則無數據丟失/重複 |
| 確定性隨機種子 | 可複現、可驗證,避免真隨機導致測試不穩定 |
| 大數據量(10萬/線程) | 放大併發 Bug 概率(ABA、競態、內存可見性) |
OpenJDK 官方併發壓測框架 JCStress(org.openjdk.jcstress)提供聲明式測試:
@JCStressTest
@State
@Actor
public class BufferTest {
BoundedBuffer buf = new BoundedBuffer<>(10);
@Actor public void producer(int[] r) { buf.put(1); }
@Actor public void consumer(int[] r) { r[0] = buf.take(); }
@Arbiter public void arbiter(int[] r) { /* 驗證 */ }
}
比手寫框架更規範、更易併入 CI/CD。
併發測試三板斧:確定性隨機源 + 同步起跑柵欄 + 無鎖結果驗證。掌握 BoundedBuffer 雙信號量設計與 PutTakeTest 雙柵欄驗證模板,即可為任意併發數據結構編寫高可信度測試。