-
正确性测试
(0) 要测试的类
SemaphoreBoundedBuffer.java
@ThreadSafe public class SemaphoreBoundedBuffer<E> { private final Semaphore availableItems, availableSpaces; @GuardedBy("this") private final E[] items; @GuardedBy("this") private int putPosition = 0, takePosition = 0; public SemaphoreBoundedBuffer(int capacity) { if (capacity <= 0) { throw new IllegalArgumentException(); } availableItems = new Semaphore(0); availableSpaces = new Semaphore(capacity); items = (E[]) new Object[capacity]; } public boolean isEmpty() { return availableItems.availablePermits() == 0; } public boolean isFull() { return availableSpaces.availablePermits() == 0; } public void put(E x) throws InterruptedException { availableSpaces.acquire(); doInsert(x); availableItems.release(); } public E take() throws InterruptedException { availableItems.acquire(); E item = doExtract(); availableSpaces.release(); return item; } private synchronized void doInsert(E x) { int i = putPosition; items[i] = x; putPosition = (++i == items.length) ? 0 : i; } private synchronized E doExtract() { int i = takePosition; E x = items[i]; items[i] = null; takePosition = (++i == items.length) ? 0 : i; return x; } }
(1) 基本的单元测试
串行测试, 目标是找出与并发性无关的问题
@Test public void testIsEmptyWhenConstructed() { SemaphoreBoundedBuffer<Integer> bb = new SemaphoreBoundedBuffer<Integer>(10); Assert.assertTrue(bb.isEmpty()); Assert.assertFalse(bb.isFull()); } @Test public void testIsFullAfterPuts() throws InterruptedException { SemaphoreBoundedBuffer<Integer> bb = new SemaphoreBoundedBuffer<Integer>(10); for (int i = 0; i < 10; i++) { bb.put(i); } Assert.assertTrue(bb.isFull()); Assert.assertFalse(bb.isEmpty()); }
(2) 对阻塞操作的测试
1° 要测试一个方法的阻塞行为, 类似于测试一个抛出异常的方法, 如果这个方法可以正常返回, 那么就意味着测试失败
2° 如果
@Test public void testTakeBlocksWhenEmpty() { final SemaphoreBoundedBuffer<Integer> bb = new SemaphoreBoundedBuffer<Integer>(10); Thread taker = new Thread() { public void run() { try { int unused = bb.take(); Assert.fail(); // if we get here, it's an error } catch (InterruptedException success) { } } }; try { taker.start(); Thread.sleep(LOCKUP_DETECT_TIMEOUT); taker.interrupt(); taker.join(LOCKUP_DETECT_TIMEOUT); Assert.assertFalse(taker.isAlive()); } catch (Exception unexpected) { Assert.fail(); } }
(3) 安全性测试
创建多个线程分别执行take和put操作, 检查放入队列和从队列中取出的各个元素
一种实现是利用校验和
public class PutTakeTest extends TestCase { protected static final ExecutorService pool = Executors.newCachedThreadPool(); protected CyclicBarrier barrier; protected final SemaphoreBoundedBuffer<Integer> bb; protected final int nTrials, nPairs; protected final AtomicInteger putSum = new AtomicInteger(0); protected final AtomicInteger takeSum = new AtomicInteger(0); public PutTakeTest(int capacity, int npairs, int ntrials) { this.bb = new SemaphoreBoundedBuffer<Integer>(capacity); this.nTrials = ntrials; this.nPairs = npairs; this.barrier = new CyclicBarrier(npairs * 2 + 1); } public static void main(String[] args) throws Exception { new PutTakeTest(10, 10, 100000).test(); // sample parameters pool.shutdown(); } void test() { try { for (int i = 0; i < nPairs; i++) { pool.execute(new Producer()); pool.execute(new Consumer()); } barrier.await(); // wait for all threads to be ready barrier.await(); // wait for all threads to finish assertEquals(putSum.get(), takeSum.get()); } catch (Exception e) { throw new RuntimeException(e); } } static int xorShift(int y) { y ^= (y << 6); y ^= (y >>> 21); y ^= (y << 7); return y; } class Producer implements Runnable { public void run() { try { int seed = (this.hashCode() ^ (int) System.nanoTime()); int sum = 0; barrier.await(); for (int i = nTrials; i > 0; --i) { bb.put(seed); sum += seed; seed = xorShift(seed); } putSum.getAndAdd(sum); barrier.await(); } catch (Exception e) { throw new RuntimeException(e); } } } class Consumer implements Runnable { public void run() { try { barrier.await(); int sum = 0; for (int i = nTrials; i > 0; --i) { sum += bb.take(); } takeSum.getAndAdd(sum); barrier.await(); } catch (Exception e) { throw new RuntimeException(e); } } } }
之所以要使用随机产生数据的方法, 原因是一个"智能的"编译器对于总是相同的测试结果很有可能可以猜出结果, 这样就测试不出问题了;
如果每个线程创建后立刻start()的话, 最坏的情况是所有线程都串行执行, 这样也测不出问题; 所以__使用栅栏CyclicBarrier, 来让所有线程都从同一起跑线出发__
(4) 资源管理的测试
限制缓存的大小, 防止由于资源耗尽而导致应用程序发生故障
@Test public void testLeak() throws InterruptedException { SemaphoreBoundedBuffer<Big> bb = new SemaphoreBoundedBuffer<Big>(CAPACITY); int heapSize1 = snapshotHeap(); for (int i = 0; i < CAPACITY; i++) { bb.put(new Big()); } for (int i = 0; i < CAPACITY; i++) { bb.take(); } int heapSize2 = snapshotHeap(); Assert.assertTrue(Math.abs(heapSize1 - heapSize2) < THRESHOLD); }
在SemaphoreBoundedBuffer的take()方法中, 会将items[i] = null;这样可以触发GC机制
(5) 产生更多的交替操作
产生更多的上下文切换, 以便更容易暴露并发中的问题
1° 使用Thread.yield()
这个方法和平台相关, JVM完全可以实现为一个空操作
2° 使用 Thread.sleep(xxx)
睡眠一个短的时间, 虽然更慢但是比较可靠
chapter12_并发程序的测试_1_正确性测试
猜你喜欢
转载自blog.csdn.net/captxb/article/details/88621465
今日推荐
周排行