两种并发测试
1.安全性测试:不发生任何错误的行为。
2.活跃性测试:某个良好的行为终究会发生。
12.1 正确性测试
测试分析点:不变性条件 和 后验条件
程序清单12-1 基于信号量的有界缓存
package com.multithread.unit12;
import java.util.concurrent.Semaphore;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
/**
* Semaphore
* release()将创建一个许可
* acquire()将消耗一个许可
* 在实际方法中,如果需要一个有界缓存,应该直接使用ArrayBlockingQueue,LinkedBlockingQueue,而不是自己编写。
* 但这里是用于说明如何对添加和删除等方法进行控制,在其他数据结构中同样可以使用
*
* 不变性条件:
* 1) avaliableItems 和 avaliableSpaces 的和 永远等于 缓存的大小
* 2) 新建的缓存是空的,而不是满的
*/
@ThreadSafe
public class BoundedBuffer<E> {
//可以从缓存中删除的元素个数,可插入到缓存的元素个数
private final Semaphore avaliableItems,avaliableSpaces;
@GuardedBy("this") private final E[] items;
@GuardedBy("this") private int putPosition = 0, takePosition = 0;
public BoundedBuffer(int capacity) {
this.avaliableItems = new Semaphore(0);
this.avaliableSpaces = new Semaphore(capacity);
this.items = (E[])new Object[capacity];
}
/**
* 可以从缓存中删除的元素个数为0,证明集合里没有元素,是空集合
*/
public boolean isEmpty() {
return avaliableItems.availablePermits() == 0;
}
/**
* 可插入到缓存的元素个数为0,证明集合已被填满,所以没有空间了
*/
public boolean isFull() {
return avaliableSpaces.availablePermits() == 0;
}
/**
* 阻塞式:往集合中新增加一个元素
*/
public void put(E e) throws InterruptedException {
avaliableSpaces.acquire(); //剩余集合空间被用掉一个(消耗一个许可)
doInsert(e);
avaliableItems.release(); //可用的元素个数新增一个(创建一个许可)
}
/**
* 阻塞式:从集合中获取一个元素
*/
public E take() throws InterruptedException {
//从可以从缓存中 --> 可删除的元素个数信的号量中 获取一个许可(Permit),元素被拿走一个
//如果缓存不为空,那么这个请求会立即成功,继续往下执行,
//否则请求被阻塞直到缓存不为空:(即avaliableItems信号量中有许可了)
avaliableItems.acquire();
E e = doExtract();
avaliableSpaces.release(); //集合剩余的未被使用的元素坑位多一个(许可被释放)
return e;
}
/**
* 删除缓存中的下一个元素
*/
private E doExtract() {
int i = takePosition;
E x = items[i];
items[i] = null;
takePosition = (++i == items.length) ? 0 : i;
return x;
}
private synchronized void doInsert(E e) {
int i = putPosition;
items[i] = e;
putPosition = (++i == items.length) ? 0 : i;
}
}
12.1.1基本的单元测试
程序清单12-2 BoundedBuffer的基本单元测试
package com.multithread.unit12;
import org.junit.Test;
import junit.framework.TestCase;
public class TestBounded {
@Test
public void testIsEmptyWhenContructed() {
BoundedBuffer<?> buffer = new BoundedBuffer<>(10);
TestCase.assertTrue(buffer.isEmpty());
TestCase.assertFalse(buffer.isFull());
}
@Test
public void testIsFullAfterPush() throws InterruptedException {
BoundedBuffer<Integer> buffer = new BoundedBuffer<>(10);
for(int i = 0; i < 10; i++)
buffer.put(i);
//buffer.take();
TestCase.assertTrue(buffer.isFull());
TestCase.assertFalse(buffer.isEmpty());
}
}
12.1.2 对阻塞操作的测试
程序清单12-3 测试阻塞行为以及对中断的响应性
@Test
public void testTakeBlocksWhenEmpty() {
final BoundedBuffer<Integer> buffer = new BoundedBuffer<>(10);
/**
* 创建一个”获取线程“,尝试从一个空缓存中获取一个元素。
*
*/
Thread taker = new Thread() {
@Override
public void run() {
try {
Integer unused = buffer.take(); //如果take成功,代表测试失败。
fail(); //如果执行到这里,代表发生了一个错误
} catch (InterruptedException e) { //如果take()方法正确地被阻塞了,将会抛出此异常,源自信号量被阻塞的异常
System.out.println("恭喜!!! 空缓存成功被阻塞。");
}
}
};
try {
taker.start();
Thread.sleep(LOCKUP_DETECT_TIMEOUT); //等待一段时间
taker.interrupt(); //中断该线程
System.out.println("线程被中断");
taker.join(LOCKUP_DETECT_TIMEOUT); //主线程尝试与”获取“线程合并
//验证join方法是否成功返回,如果”获取“线程可以响应中断,那么join能很快完成
TestCase.assertFalse(taker.isAlive());
} catch(Exception e) {
fail();
}
}
public void fail() {
System.out.println("You are fail");
}
12.1.3 安全性测试(没看懂)
package com.multithread.unit12;
import java.util.concurrent.BrokenBarrierException;
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;
/**程序报如下异常
* Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-16" java.lang.NullPointerException
at com.multithread.unit12.PutTakeTest$Consumer.run(PutTakeTest.java:90)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
.......
*/
//测试清单 12-5 测试BoundedBuffer的生产者-消费者程序
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 CyclicBarrier barrier;
private final BoundedBuffer<Integer> bb;
private final int nTrails,nPairs;
PutTakeTest(int capacity, int nPairs, int nTrails) {
this.bb = new BoundedBuffer<>(capacity);
this.nTrails = nTrails;
this.nPairs = nPairs;
this.barrier = new CyclicBarrier(nPairs * 2 + 1);
}
public void test() {
try {
for (int i = 0; i < nPairs; i++) {
pool.execute(new Producer());// n个生产者线程生成元素并把他们放入队列
pool.execute(new Consumer());// n个消费者线程消费元素
}
barrier.await();//等待所有的线程就绪
barrier.await();//等待所有的线程执行完成
TestCase.assertEquals(putSum.get(), takeSum.get());
// System.out.println(putSum.get() ==takeSum.get());
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
}
//程序清单 12-4 适合在测试中使用的随机数生成器
static int xorShift(int y) {
y ^= (y << 6);
y ^= (y >>> 21);
y ^= (y << 7);
return y;
}
public static void main(String[] args) {
new PutTakeTest(10, 10, 100000).test();//示例参数
pool.shutdown();
}
/**
* 每个线程都有一个校验和,并在测试合并后将他们合并起来
* 从而在测试缓存时就不会引入过多的同步或竞争???
*/
//程序清单2-6 在PutTakeTest中使用的生产者和消费者
/**生产者*/
class Producer implements Runnable {
@Override
public void run() {
try {
int seed = (this.hashCode() ^ (int)System.nanoTime()); //生成随机数
int sum = 0;
barrier.await();
for (int i = nTrails; i > 0; --i) {
bb.put(seed); //随机数 元素进队列
sum += seed; //随机数元素累加
seed = xorShift(seed); //生成新的随机数
}
putSum.getAndAdd(sum); //更新元素计算得到的校验和
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
}
}
/**消费者*/
class Consumer implements Runnable {
@Override
public void run() {
try {
barrier.await();
int sum = 0;
for (int i = nTrails; i > 0; --i) {
sum += bb.take(); //元素出队列
}
takeSum.getAndAdd(sum); //更新元素计算得到的校验和
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
}
}
}