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

《Java 併發編程實戰》第 10 章:併發程序測試實戰——從隨機數生成到生產者消費者驗證框架

     編輯:Java 綜合教學

1. 高效無鎖隨機數生成器(XORShift)

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+ 內建,實際生產首選)。

2. 基於信號量的有界緩衝隊列


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 整體鎖。

3. 併發測試驗證框架

核心組件

  • 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、競態、內存可見性)

現代化補充:JCStress

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 雙柵欄驗證模板,即可為任意併發數據結構編寫高可信度測試。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved