chapter12_并发程序的测试_1_正确性测试

  • 正确性测试

    (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)

    睡眠一个短的时间, 虽然更慢但是比较可靠

猜你喜欢

转载自blog.csdn.net/captxb/article/details/88621465