首页 > 代码库 > Java 并发编程之测试(二)

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。

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


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






Java 并发编程之测试(二)