《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");
                        t.test();
                        System. out .println("\t" );
                        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 分析与监測工具

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

时间: 2024-10-29 19:06:54

《Java并发编程实战》第十二章 测试并发程序 读书笔记的相关文章

《Java并发编程实战》第三章 对象的共享 读书笔记

一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见性的描写叙述即多线程能够实时获取其它线程改动后的状态. *** 待补充   两个工人同一时候记录生产产品总数问题 1. 失效数据 可见性出现故障就是其它线程没有获取到改动后的状态,更直观的描写叙述就是其它线程获取到的数据是失效数据. 2. 非原子64位操作 3. 加锁与可见性 比如在一个变量的读取与

《Java并发编程实战》第四章 对象的组合 读书笔记

一.设计线程安全的类 在设计线程安全类的过程中,须要包括下面三个基本要素: . 找出构成对象状态的全部变量. . 找出约束状态变量的不变性条件. . 建立对象状态的并发訪问管理策略. 分析对象的状态,首先从对象的域開始. 变量按作用域划分: . 全局变量 . 局部变量 . 方法行參 . 异常处理參数 1. 收集同步需求 假设不了解对象的不变性条件与后验条件,那么就不能确保线程安全性.要满足在状态变量的有效值或状态转换上的各种约束条件.就须要借助原子性和封装性. 说的更简略些是Java线程安全都是

《Java并发编程实战》第七章 取消与关闭 读书笔记

Java没有提供不论什么机制来安全地(抢占式方法)终止线程,尽管Thread.stop和suspend等方法提供了这种机制,可是因为存在着一些严重的缺陷,因此应该避免使用. 但它提供了中断Interruption机制,这是一种协作机制,可以使一个线程终止还有一个线程的当前工作. 一.任务取消 取消操作的原因: . 用户请求取消 . 有时间限制的操作 . 应用程序事件 . 错误 . 关闭 结束任务的四种方式: 1. run方法运行结束 2. 使用请求关闭标记(比如boolean开关) 3. 使用中

《Java并发编程实战》第八章 线程池的使用 读书笔记

一.在任务与执行策略之间的隐性解耦 有些类型的任务需要明确地指定执行策略,包括: . 依赖性任务.依赖关系对执行策略造成约束,需要注意活跃性问题.要求线程池足够大,确保任务都能放入. . 使用线程封闭机制的任务.需要串行执行. . 对响应时间敏感的任务. . 使用ThreadLocal的任务. 1. 线程饥饿死锁 线程池中如果所有正在执行任务的线程都由于等待其他仍处于工作队列中的任务而阻塞,这种现象称为线程饥饿死锁. 2. 运行时间较长的任务 Java提供了限时版本与无限时版本.例如Thread

《Java并发编程实战》第十章 避免活跃性危险 读书笔记

一.死锁 所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去. 百科百科 当两个以上的运算单元,双方都在等待对方停止运行,以取得系统资源,但是没有一方提前退出时,这种状况,就称为死锁.维基百科 1. 顺序死锁 最少有两个锁,一个线程获取到A锁需要获取B锁才能进行操作,而另外一个线程获取到了B锁,需要获取A锁才能执行操作,这种情况下容易出现顺序死锁. public class LeftRightDeadlock { priva

《Java并发编程实战》第十章 避免活跃性危急 读书笔记

一.死锁 所谓死锁: 是指两个或两个以上的进程在运行过程中.因争夺资源而造成的一种互相等待的现象.若无外力作用.它们都将无法推进下去. 百科百科 当两个以上的运算单元,两方都在等待对方停止执行,以取得系统资源,可是没有一方提前退出时,这样的状况,就称为死锁.维基百科 1. 顺序死锁 最少有两个锁.一个线程获取到A锁须要获取B锁才干进行操作,而另外一个线程获取到了B锁.须要获取A锁才干运行操作.这样的情况下easy出现顺序死锁. public class LeftRightDeadlock { p

《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

造成开销的操作包括: 1. 线程之间的协调(例如:锁.触发信号以及内存同步等) 2. 增加的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 运行速度涉及以下两个指标: 某个指定的任务单元需要"多快"才能处理完成.计算资源一定的情况下,能完成"多少"工作. 可伸缩性: 当增加计算资源时(例如:CPU.内存.存储容器或I/O带宽),程序的吞吐量或者处理能力能相应地增加. 2 评估各种性能权衡因素 避免不成熟的优化.首先使程序正

[Java并发编程实战]构建一个高效可复用缓存程序(含代码)

[Java并发编程实战]构建一个高效可复用缓存程序(含代码) 原文地址:https://www.cnblogs.com/chengpeng15/p/9915800.html

Java 线程第三版 第六章 高级同步议题 读书笔记

多线程数据同步错误比较难检测,因为通常是与事件的特定发生顺序有关. 一.同步术语 Barrier(屏障) barrier是多个Thread的集合点:所有的Thread都应该到齐在这个barrier之后才能允许它们继续下去. Condition variable(条件变量) 实际上不是变量,而是与某个lock有关联的变量. Event variable(事件变量) 条件变量的另一个名称. Critical section(临界区) 临界区是synchronized方法或者block. Lock(锁