并发测试大致分为两类:安全性测试(不发生任何错误的行为)和活跃性测试(某个良好的行为终究会发生)。
安全测试 - 通常采用测试不变性条件的形式,即判断某个类的行为是否与其他规范保持一致。
活跃性测试 - 包括进展测试和无进展测试两个方面。
性能测试与活跃性测试相关,主要包括:吞吐量、响应性、可伸缩性。
一、正确性测试
找出需要检查的不变条件和后延条件。
[java]view plaincopy
print?
importjava.util.concurrent.Semaphore;
publicclassBoundedBuffer {
privatefinalSemaphore availableItems, availableSpaces;
privatefinalE[] items;
privateintputPosition =0;
privateinttakePosition =0;
@SuppressWarnings("unchecked")
publicBoundedBuffer(intcapacity) {
availableItems =newSemaphore(0);
availableSpaces =newSemaphore(capacity);
items = (E[])newObject[capacity];
}
publicbooleanisEmpty() {
returnavailableItems.availablePermits() ==0;
}
publicbooleanisFull() {
returnavailableSpaces.availablePermits() ==0;
}
publicvoidput(E x)throwsInterruptedException {
availableSpaces.acquire();
doInsert(x);
availableItems.release();
}
publicE take()throwsInterruptedException {
availableItems.acquire();
E item = doExtract();
availableSpaces.release();
returnitem;
}
privatesynchronizedvoiddoInsert(E x) {
inti = putPosition;
items[i] = x;
putPosition = (++i == items.length)?0: i;
}
privatesynchronizedE doExtract() {
inti = takePosition;
E x = items[i];
items[i] =null;
takePosition = (++i == items.length)?0: i;
returnx;
}
}
1 基本的单元测试
[java]view plaincopy
print?
importstaticorg.junit.Assert.*;
importorg.junit.Test;
publicclassBoundedBufferTests {
@Test
publicvoidtestIsEmptyWhenConstructed() {
BoundedBuffer bb =newBoundedBuffer(10);
assertTrue(bb.isEmpty());
assertFalse(bb.isFull());
}
@Test
publicvoidtestIsFullAfterPuts()throwsInterruptedException {
BoundedBuffer bb =newBoundedBuffer(10);
for(inti =0; i <10; i++) {
bb.put(i);
}
assertTrue(bb.isFull());
assertTrue(bb.isEmpty());
}
}
2 对阻塞操作的测试
take方法是否阻塞、中断处理。从空缓存中获取一个元素。
[java]view plaincopy
print?
@Test
publicvoidtestTakeBlocksWhenEmpty(){
finalBoundedBuffer bb =newBoundedBuffer(10);
Thread taker =newThread(){
@Override
publicvoidrun() {
try{
intunused = 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 安全性测试
在构建对并发类的安全性测试中,需要解决地关键性问题在于,要找出那些容易检查的属性,这些属性在发生错误的情况下极有可能失败,同时又不会使得错误检查代码人为地限制并发性。理想情况是,在测试属性中不需要任何同步机制。
[java]view plaincopy
print?
importjava.util.concurrent.CyclicBarrier;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
importjava.util.concurrent.atomic.AtomicInteger;
importjunit.framework.TestCase;
publicclassPutTakeTestextendsTestCase {
privatestaticfinalExecutorService pool = Executors.newCachedThreadPool();
privatefinalAtomicInteger putSum =newAtomicInteger(0);
privatefinalAtomicInteger takeSum =newAtomicInteger(0);
privatefinalCyclicBarrier barrier;
privatefinalBoundedBuffer bb;
privatefinalintnTrials, nPairs;
publicstaticvoidmain(String[] args) {
newPutTakeTest(10,10,100000).test();// 示例参数
pool.shutdown();
}
staticintxorShift(inty) {
y ^= (y <<6);
y ^= (y >>>21);
y ^= (y <<7);
returny;
}
publicPutTakeTest(intcapacity,intnPairs,intnTrials) {
this.bb =newBoundedBuffer(capacity);
this.nTrials = nTrials;
this.nPairs = nPairs;
this.barrier =newCyclicBarrier(nPairs *2+1);
}
voidtest() {
try{
for(inti =0; i < nPairs; i++) {
pool.execute(newProducer());
pool.execute(newConsumer());
}
barrier.await();// 等待所有的线程就绪
barrier.await();// 等待所有的线程执行完成
assertEquals(putSum.get(), takeSum.get());
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
classProducerimplementsRunnable {
@Override
publicvoidrun() {
try{
intseed = (this.hashCode() ^ (int) System.nanoTime());
intsum =0;
barrier.await();
for(inti = nTrials; i >0; --i) {
bb.put(seed);
sum += seed;
seed = xorShift(seed);
}
putSum.getAndAdd(sum);
barrier.await();
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
}
classConsumerimplementsRunnable {
@Override
publicvoidrun() {
try{
barrier.await();
intsum =0;
for(inti = nTrials; i >0; --i) {
sum += bb.take();
}
takeSum.getAndAdd(sum);
barrier.await();
}catch(Exception e) {
thrownewRuntimeException(e);
}
}
}
}
4 资源管理的测试
对于任何持有或管理其他对象的对象,都应该在不需要这些对象时销毁对他们的引用。测试资源泄露的例子:
[java]view plaincopy
print?
classBig {
double[] data =newdouble[100000];
};
voidtestLeak()throwsInterruptedException{
BoundedBuffer bb =newBoundedBuffer(CAPACITY);
intheapSize1 =/* 生成堆的快照 */;
for(inti =0; i < CAPACITY; i++){
bb.put(newBig());
}
for(inti =0; i < CAPACITY; i++){
bb.take();
}
intheapSize2 =/* 生成堆的快照 */;
assertTrue(Math.abs(heapSize1 - heapSize2) < THRESHOLD);
}
5 使用回调
6 产生更多的交替操作
二、性能测试
性能测试的目标 - 根据经验值来调整各种不同的限值。例如:线程数量、缓存容量等。
1 在PutTakeTest中增加计时功能
基于栅栏的定时器
[java]view plaincopy
print?
this.timer =newBarrierTimer();
this.barrier =newCyclicBarrier(nPairs *2+1, timer);
publicclassBarrierTimerimplementsRunnable{
privatebooleanstarted ;
privatelongstartTime ;
privatelongendTime ;
@Override
publicsynchronizedvoidrun() {
longt = System.nanoTime();
if(!started ){
started =true;
startTime = t;
}else{
endTime = t;
}
}
publicsynchronizedvoidclear(){
started =false;
}
publicsynchronizedlonggetTime(){
returnendTime - startTime;
}
}
修改后的test方法中使用了基于栅栏的计时器
[java]view plaincopy
print?
voidtest(){
try{
timer.clear();
for(inti =0; i < nPairs; i++){
pool .execute(newProducer());
pool .execute(newConsumer());
}
barrier .await();
barrier .await();
longnsPerItem = timer.getTime() / ( nPairs * (long)nTrials );
System. out .println("Throughput: "+ nsPerItem +" ns/item");
assertEquals(putSum.get(), takeSum.get() )
}catch(Exception e) {
thrownewRuntimeException(e);
}
. 生产者消费者模式在不同参数组合下的吞吐率
. 有界缓存在不同线程数量下的伸缩性
. 如何选择缓存的大小
[java]view plaincopy
print?
publicstaticvoidmain(String[] args)throwsInterruptedException {
inttpt =100000;// 每个线程中的测试次数
for(intcap =1; cap <= tpt; cap *=10){
System. out .println("Capacity: "+ cap);
for(intpairs =1; pairs <=128; pairs *=2){
TimedPutTakeTest t =newTimedPutTakeTest(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 分析与监测工具
以上就是我推荐给Java开发者们的一面试经典知识。但是这些知识里面并没有太多Java全栈、Java晋阶、JAVA架构之类的题,不是我不推荐,而是希望大家更多的从基本功做起,打好基础,太多复杂的内容一会儿也说不明白。
好了同学们,我能介绍的也都全部介绍完给你们了,如果下获得更多JAVA教学资源,可以选择来我们这里共同交流,群:240448376,很多大神在这里切磋学习,不懂可以直接问,晚上还有大牛免费直播教学。
注:加群要求
1、具有一定工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加,有些应届生和实习生也可以加。
2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。
3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。
4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。
5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!
PS:现在主要讲解的内容是(反射原理、枚举原理与应用、注解原理、常用设计模式、正规表达式高级应用、JAVA操作Office原理详解、JAVA图像处理技术,等多个知识点的详解和实战)
6.小号或者小白之类加群一律不给过,谢谢。
最后,每一位读到这里的网友,感谢你们能耐心地看完。觉得对你有帮助可以给个喜欢!希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步