测试的两个方面:
- 安全性 并发不会造成状态错误。
- 活跃度 主要是性能测试
- 吞吐量 段时间定资源内可以处理的任务数量
- 响应性 从请求到完成一些动作之前的延迟(等待执行的时间)
- 可伸缩性 增加资源,提高性能
正确性测试
所有加入队列的都会被执行:
一般的可行的办法是把加入队列的列表和消费的列表保存起来进行比较。但是因为会设计资源的并发,影响对本身的测试,比较好的办法是在队列中加入唯一的id,然后对id求和,消费时同样求和,来比较结果。这样能最小的影响实际要测试的结果。
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 int nTrials, nPairs;
private final BlockingQueue<Integer> queue;
public static void main(String[] args) {
new PutTakeTest(10000).test();
}
public PutTakeTest(int nTrials){
nPairs = 10; // 生产者及消费者的个数
this.nTrials = nTrials; // 每次生产或者消费多少个
queue = new ArrayBlockingQueue<Integer>(10);
}
public void test(){
for (int i = 0; i < nPairs; i++) {
pool.execute(new Producer());
pool.execute(new Consumer());
}
pool.shutdown();
try {
pool.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(putSum.get());
System.out.println(takeSum.get());
}
class Producer implements Runnable{
@Override
public void run() {
int sum = 0;
try {
for (int i = 0; i < nTrials; i++) {
int seed = new Random().nextInt(10000); // 随机数,用来做sum,最后跟消费者的sum做比较来确定所有入队的都已经被出队消费
sum += seed;
queue.put(seed);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
putSum.getAndAdd(sum);
}
}
class Consumer implements Runnable{
@Override
public void run() {
int sum = 0;
try {
for (int i = 0; i < nTrials; i++) {
int seed = queue.take();
sum += seed;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
takeSum.getAndAdd(sum);
}
}
}
按照上面的代码,实际上出于并行状态的线程并不多,可以使用关卡这种类型的信号量,来让所有的线程同时启动,再同时计算,提升并发的操作。
性能
吞吐量
基于上面的代码,其实在最开始和最后加入时间差的计算就可以粗略估算了
另外还可以借助关卡的特性,在最后回调的时候得到时间
响应性
即完成一个任务所需要的时间
性能测试陷阱
如果测试过程中恰巧多次出发了垃圾回收,那么就有可能把gc的时间算到实际运行的实践中。解决办法
- 使用-verbose:gc来停止垃圾回收的运行
- 第二种就是运行足够长的时间了
另外动态编译也可能会造成数据的不准确,可以使用-xx:+PrintCompilation来查看动态编译的时间,长时间运行测试程序,减少动态编译的影响
另外要考虑不且实际的竞争程度, 影响吞吐量通常是因为边界清晰小人物,大部分的时间都来源于上线问切换,而对于一些锁竞争比较多的程序可能影响最大的是锁竞争的程度,要根据不同情况制定具体的策略。
代码检查
- 调用Thread.run,应该是.start
- 显示锁未释放
- 空synchronized块
- 双检查锁, 因为可见性的问题及重排序的问题
- 从构造函数中启动线程,造成了this的溢出
- notify通常伴随着状态改变使用
- wait方法通常会出现在持有锁,循环以及含有测试某个状态的谓词(if())
- Thread.sleep会持有锁会对活跃度造成影响要注意
- 忙等待 while(a.equals(“”))这样的结果,如果域不是volatile的可能不会及时得到状态变更,可以使用锁通知等更好的方式。