zoukankan      html  css  js  c++  java
  • 《Java并发编程实战》第十二章 测试并发程序 读书笔记


    并发测试分为两类:安全性测试(无论错误的行为不会发生)而活性测试(会发生)。
    安全測试 - 通常採用測试不变性条件的形式,即推断某个类的行为是否与其它规范保持一致。

    活跃性測试 - 包含进展測试和无进展測试两个方面。

    性能測试与活跃性測试相关,主要包含:吞吐量、响应性、可伸缩性。

    一、正确性測试

    找出须要检查的不变条件和后延条件。
    import java.util.concurrent.Semaphore;
    
    public class BoundedBuffer<E> {
    
    	private final Semaphore availableItems, availableSpaces;
    	private final E[] items;
    	private int putPosition = 0;
    	private int takePosition = 0;
    
    	@SuppressWarnings("unchecked")
    	public BoundedBuffer(int capacity) {
    		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 主要的单元測试

    import static org.junit.Assert.*;
    import org.junit.Test;
    
    public class BoundedBufferTests {
    
    	@Test
    	public void testIsEmptyWhenConstructed() {
    		BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
    		assertTrue(bb.isEmpty());
    		assertFalse(bb.isFull());
    	}
    
    	@Test
    	public void testIsFullAfterPuts() throws InterruptedException {
    		BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
    		for (int i = 0; i < 10; i++) {
    			bb.put(i);
    		}
    		assertTrue(bb.isFull());
    		assertTrue(bb.isEmpty());
    	}
    }



    2 对堵塞操作的測试
    take方法是否堵塞、中断处理。从空缓存中获取一个元素。
    	@Test
    	public void testTakeBlocksWhenEmpty(){
    		final BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
    		Thread taker = new Thread(){
    			@Override
    			public void run() {
    				try {
    					int unused =  bb.take();
    					fail(); //假设运行到这里。那么表示出现了一个错误
    				} catch (InterruptedException e) { }
    			}
    		};
    		try {
    			taker.start();
    			Thread.sleep(LOCKUP_DETECT_TIMEOUT);
    			taker.interrupt();
    			taker.join(LOCKUP_DETECT_TIMEOUT);
    			assertFalse(taker.isAlive());
    		} catch (InterruptedException e) {
    			fail();
    		}
    	}

    创建一个“获取”线程,该线程将尝试从空缓存中获取一个元素。
    假设take方法成功,那么表示測试失败。
    运行測试的线程启动“获取”线程。等待一段时间,然后中断该线程。

    假设“获取”线程正确地在take方法中堵塞。那么将抛出InterruptedException。而捕获到这个异常的catch块将把这个异常视为測试成功,并让线程退出。

    然后,主測试线程会尝试与“获取”线程合并,通过调用Thread.isAlive来验证join方法是否成功返回,假设“获取”线程能够响应中断。那么join能非常快地完毕。


    使用Thread.getState来验证线程是否能在一个条件等待上堵塞,但这样的方法并不可靠。

    被堵塞线程并不须要进入WAITING或者TIMED_WAITING等状态,因此JVM能够选择通过自旋等待来实现堵塞。


    3 安全性測试
    在构建对并发类的安全性測试中,须要解决地关键性问题在于,要找出那些easy检查的属性,这些属性在错误发生的情况下极有可能失败,同一时候又不会使得错误检查代码人为地限制并发性。理想情况是,在測试属性中不须要不论什么同步机制。

    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    import junit.framework.TestCase;
    
    public class PutTakeTest extends TestCase {
    	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> bb;
    	private final int nTrials, nPairs;
    
    	public static void main(String[] args) {
    		new PutTakeTest(10, 10, 100000).test(); // 演示样例參数
    		pool.shutdown();
    	}
    
    	static int xorShift(int y) {
    		y ^= (y << 6);
    		y ^= (y >>> 21);
    		y ^= (y << 7);
    		return y;
    	}
    
    	public PutTakeTest(int capacity, int nPairs, int nTrials) {
    		this.bb = new BoundedBuffer<Integer>(capacity);
    		this.nTrials = nTrials;
    		this.nPairs = nPairs;
    		this.barrier = new CyclicBarrier(nPairs * 2 + 1);
    	}
    
    	void test() {
    		try {
    			for (int i = 0; i < nPairs; i++) {
    				pool.execute(new Producer());
    				pool.execute(new Consumer());
    			}
    			barrier.await(); // 等待全部的线程就绪
    			barrier.await(); // 等待全部的线程运行完毕
    			assertEquals(putSum.get(), takeSum.get());
    		} catch (Exception e) {
    			throw new RuntimeException(e);
    		}
    	}
    
    	class Producer implements Runnable {
    		@Override
    		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 {
    		@Override
    		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);
    			}
    		}
    	}
    }

    4 资源管理的測试
    对于不论什么持有或管理其它对象的对象,都应该在不须要这些对象时销毁对他们的引用。測试资源泄露的样例:
    class Big {
    	double[] data = new double[100000];
    };
    
    void testLeak() throws InterruptedException{
    	BoundedBuffer<Big> bb = new BoundedBuffer<Big>(CAPACITY);
    	int heapSize1 = /* 生成堆的快照 */;
    	for (int i = 0; i < CAPACITY; i++){
    		bb.put(new Big());
    	}
    	for (int i = 0; i < CAPACITY; i++){
    		bb.take();
    	}
    	int heapSize2 = /* 生成堆的快照 */;
    	assertTrue(Math.abs(heapSize1 - heapSize2) < THRESHOLD);
    }

    5 使用回调
    6 产生很多其它的交替操作

    二、性能測试

    性能測试的目标 - 依据经验值来调整各种不同的限值。比如:线程数量、缓存容量等。

    1 在PutTakeTest中添加计时功能
    基于栅栏的定时器
           this .timer = new BarrierTimer();
           this .barrier = new CyclicBarrier(nPairs * 2 + 1, timer);
    
           public class BarrierTimer implements Runnable{
                 private boolean started ;
                 private long startTime ;
                 private long endTime ;
                      
                 @Override
                 public synchronized void run() {
                       long t = System.nanoTime();
                       if (!started ){
                             started = true ;
                             startTime = t;
                      } else {
                             endTime = t;
                      }
                }
                
                 public synchronized void clear(){
                       started = false ;
                }
                
                 public synchronized long getTime(){
                       return endTime - startTime;
                }
          }



    改动后的test方法中使用了基于栅栏的计时器
           void test(){
                 try {
                       timer.clear();
                       for (int i = 0; i < nPairs; i++){
                             pool .execute( new Producer());
                             pool .execute( new Consumer());
                      }
                       barrier .await();
                       barrier .await();
                       long nsPerItem = timer.getTime() / ( nPairs * (long )nTrials );
                      System. out .println("Throughput: " + nsPerItem + " ns/item");
                      assertEquals(putSum.get(), takeSum.get() )
                } catch (Exception e) {
                       throw new RuntimeException(e);
                }
          }



    . 生产者消费者模式在不同參数组合下的吞吐率
    . 有界缓存在不同线程数量下的伸缩性
    . 怎样选择缓存的大小
           public static void main(String[] args) throws InterruptedException {
                 int tpt = 100000; // 每一个线程中的測试次数
                 for (int cap = 1; cap <= tpt; cap *= 10){
                      System. out .println("Capacity: " + cap);
                       for (int pairs = 1; pairs <= 128; pairs *= 2){
                             TimedPutTakeTest t = new TimedPutTakeTest(cap, pairs, tpt);
                            System. out .println("Pairs: " + pairs + "	");
                            t.test();
                            System. out .println("	" );
                            Thread. sleep(1000);
                            t.test();
                            System. out .println();
                            Thread. sleep(1000);
                      }
                }
                 pool .shutdown();
          }



    查看吞吐量/线程数量的关系

    2 多种算法的比較
    3 响应性衡量


    三、避免性能測试的陷阱

    1 垃圾回收
    2 动态编译
    3 对代码路径的不真实採样
    4 不真实的竞争程度
    5 无用代码的消除


    四、其它的測试方法

    1 代码审查
    2 静态分析工具 
         FindBugs、Lint
    3 面向方面的測试技术
    4 分析与监測工具






    版权声明:本文博主原创文章,博客,未经同意不得转载。

  • 相关阅读:
    Spring/Spring-Boot 学习 连接redis数据库
    Spring/Spring-Boot 学习 答疑
    Spring/Spring-Boot 学习 paoding-rose-jade 连接MySQL数据库
    Spring/Spring-Boot 学习 Bean注入的方式
    Spring/Spring-Boot 学习 @Autowired 与 @Resource
    博客园主题与代码高亮
    从不懂spring的开始的学习之旅
    nginx 缓存服务器配置
    jenkins + sonar代码质量自动化分析平台
    Linux下如何查看哪些进程占用的CPU内存资源最多
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4850070.html
Copyright © 2011-2022 走看看