Java 并发编程之测试(二)

资源管理的测试

先摆上昨天测试用的栗子

import java.util.concurrent.Semaphore;

public class BoundedBuffer<E> {

	private final Semaphore availableItems, availableSpaces;
	private final E[] Items;
	private int putPosition = 0, takePosition = 0;

	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 = doExtra();
		availableSpaces.release();
		return item;
	}

	private synchronized void doInsert(E x) {
		// TODO Auto-generated method stub
		int i = putPosition;
		Items[i] = x;
		putPosition = (++i == Items.length) ? 0 : i;
	}

	private synchronized E doExtra() {
		int i = takePosition;
		E x = Items[i];
		Items[i] = null;
		takePosition = (++i == Items.length) ? 0 : i;
		return x;
	}

}

对于像Boundedbuffer这样的类来说,资源管理的问题尤为重要。之所以要限制缓存的大小 ,其原因就是要防止由于资源耗尽而导致应用程序发生故障。例如生产者的速度远远高于消费者的速度。

通过一些测量应用程序中内存使用情况 的堆检查 工具,可以很容易地测试出对内存的不合理占用。许多商业和开源的堆分析工具中都支持这种功能 。

import static org.junit.Assert.assertTrue;
import junit.framework.Test;

public class PutTakeTest {
	final int CAPACITY = 1000;

	class Big {
		double[] big = new double[10000];
	}

	public static void main(String[] args) throws InterruptedException {
		new PutTakeTest().testLeak();
	}

	void testLeak() throws InterruptedException {
		BoundedBuffer<Big> bb = new BoundedBuffer<PutTakeTest.Big>(CAPACITY);
		long heapsize1 = Runtime.getRuntime().totalMemory();
		for (int i = 0; i < CAPACITY; i++) {
			bb.put(new Big());
		}
		for (int i = 0; i < CAPACITY; i++) {
			bb.take();
		}
		long heapsize2 = Runtime.getRuntime().totalMemory();
		System.out.println(heapsize1 + "\n" + heapsize2);
	}
}

testLeak方法将多个大型对象插入到一个有界缓存中,然后再将它们移除,第2个堆快照中的内存用量应用与第一个堆快照中的内存用量基本相同,然而,doExtra如果忘记将返回元素的引用置为空(Items[i] = null;)那在两次快照中执行的内存用量将明显不同。

不过在我实际测试的过程中,两次内存用量在将元素的引用置为空的条件下还是明显的不同,应该是没有触发system.gc。但是我们无法强制去触发垃圾回收。。所以这个测试在技术上还是有点问题的。可能对选择的垃圾收集器也有要求。我用的是CMS并且设置在内存占用达到75%才触发FullGC。

使用回调

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import javax.management.MXBean;

import org.junit.experimental.max.MaxCore;

import static org.junit.Assert.*;

public class TestingThreadFactory implements ThreadFactory {
	public final AtomicInteger numCreated = new AtomicInteger();
	private final ThreadFactory factory = Executors.defaultThreadFactory();

	public static void main(String[] args) {
		try {
			new TestingThreadFactory().testPoolExpansion();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public Thread newThread(Runnable r) {
		// TODO Auto-generated method stub
		numCreated.incrementAndGet();
		return factory.newThread(r);
	}

	public void testPoolExpansion() throws InterruptedException {
		int MAX_SIZE = 10;
		TestingThreadFactory threadFactory = new TestingThreadFactory();
		ExecutorService exec = Executors.newFixedThreadPool(MAX_SIZE,
				threadFactory);
		for (int i = 0; i < 10 * MAX_SIZE; i++) {
			exec.execute(new Runnable() {
				public void run() {
					try {
						Thread.sleep(Long.MAX_VALUE);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						Thread.currentThread().interrupt();
					}
				}
			});
		}
		for (int i = 0; i < 20 && threadFactory.numCreated.get() < MAX_SIZE; i++) {
			Thread.sleep(100);
		}
		assertEquals(threadFactory.numCreated.get(), MAX_SIZE);
		System.out.println(threadFactory.numCreated.get());
		exec.shutdown();
	}
}

上面这段代码可以测试线程池的扩展能力。在ThreadPoolExecutor中调用任务的Runnable和ThreadFactory。

在测试线程时,有以下几点,在需要更多的线程时创建新线程,在不需要时不创建 ,以及当需要回收空闲线程时执行回收操作等 。

程序中让线程进行长时间的睡眠是因为测试当把一些运行时间较长的任务提前给线程池时,线程池中的任务数量在长时间内都不会变化,这就可以进行一些判断,例如当测试线程池是否能够按照预期方式进行扩展 。

时间: 2024-12-28 23:16:21

Java 并发编程之测试(二)的相关文章

转: 【Java并发编程】之二十:并发新特性—Lock锁和条件变量(含代码)

简单使用Lock锁 Java5中引入了新的锁机制--Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接口有3个实现它的类:ReentrantLock.ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁.读锁和写锁.lock必须被显式地创建.锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例化

Java并发编程(十二):线程池的使用(转载)

本文转载自:http://www.cnblogs.com/dolphin0520/p/3932921.html 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.

Java 并发编程之测试

并发程序测试的要点 吞吐量 响应性 可伸缩性 正确性测试 首先需要一个可供测试的程序做为栗子.就是下面这个了.一个固定长度的 队列,其中定义可阻塞的put和take方法,并通过两个计数器进行控制. import java.util.concurrent.Semaphore; public class BoundedBuffer<E> { private final Semaphore availableItems, availableSpaces; private final E[] Item

Java并发编程系列之二十五:线程池

线程池简介 在之前介绍Executor框架的文章中对线程池有一个初步的认识,实际上线程池这种设计思想在Java中很普遍,比如JVM中常量池,以及Web开发使用到的数据库连接池.这些池本质上还是Java中的对象池,因为池中存放的都是Java对象.回到线程池,几乎所有需要异步或者执行并发任务的程序都可以使用到线程池.使用线程池带来的好处主要包括以下几个方面: 一,提高资源利用率.由于线程池中的线程使可以重复利用的,所以达到了循环利用的目的 二,提高响应速度.由于线程的创建也是需要开销的,如果请求到来

Java 并发编程之测试(三)

产生更多的交替操作 由于并发代码中发生的错误一般都是低概率事件,所以在测试并发错误时需要反复地执行许多次,但有些方法可以提高发现这些错误的概率 ,在前面提到过,在多处理器系统上,如果 处理器的数量少于活动线程的数量,那么 与单处理器的系统 或者 包含多个处理器的系统相比,将能产生更多的交替行为. 有一种有用的方法能提高交替操作的数量.以便能更有效的搜索程序的状态空间:就是在访问状态的操作中加上Thread.yield作为一个空操作.当代码在访问状态的时候没有使用足够的同步,将存在一些对执行时序敏

转: 【Java并发编程】之二十一:并发新特性—阻塞队列和阻塞栈(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17511147 阻塞队列 阻塞队列是Java5并发新特性中的内容,阻塞队列的接口是Java.util.concurrent.BlockingQueue,它有多个实现类:ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.PriorityBlockingQueue.SynchronousQueue等,用法大同小异,具体可查看JDK文档,这里简单

Java并发编程系列之二十八:CompletionService

CompletionService简介 CompletionService与ExecutorService类似都可以用来执行线程池的任务,ExecutorService继承了Executor接口,而CompletionService则是一个接口,那么为什么CompletionService不直接继承Executor接口呢?主要是Executor的特性决定的,Executor框架不能完全保证任务执行的异步性,那就是如果需要实现任务(task)的异步性,只要为每个task创建一个线程就实现了任务的异

Java并发编程入门(二)

1.竞态条件 1.1 定义 当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件.换句话说,正确的结果要取决于运气. 最常见的竞态条件类型:先检查后执行(Check-Then-Act)操作,即通过一个可能失效的观测结果来决定下一步的动作. 1.2 特征 与大多数并发错误一样,竞态条件并不总是会产生错误,还需要某种不恰当的执行时序. 1.3 复合操作 为了确保线程安全性,"先检查后执行"(例如延迟初始化)和"读取-修改-写入"等操作统称为复合操作:包含了

转:【Java并发编程】之二:线程中断(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17091267 使用interrupt()中断线程 当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回.这里需要注意的是,如果只是单纯的调用interrupt()方法,线程并没有实际被中断,会继续往下执行. 下面一段代码演示了休眠线程的中断: [java] view plain c