今天无意中看到以前写的代码,是一个单例的工厂模式实现,代码片段如下:
private static Map daoMap = new HashMap();
public static Dao createDao(String className) {
Dao dao = (Dao) daoMap.get(className);
if (dao != null) {
return dao;
} else {
dao = (Dao) Class.forName(className).newInstance();
daoMap.put(className, dao);
return dao;
}
}
这段代码是在一个Java EE项目的一个工厂类里发现的,今天看来,在并发稍大点儿的时候,它就会出现问题,因为HashMap是线程不安全的。于是写了段代码测试一下HashMap在并发写入时会出什么问题。
测试类:
public class Test {
static Map<String,String> map = new HashMap();
static int N = 1000;
static CountDownLatch startSignal = new CountDownLatch(1);//闭锁
static CountDownLatch doneSignal = new CountDownLatch(N);
/**
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < N; i++) {
final int j = i;
new Thread(new Runnable(){
public void run() {
try {
startSignal.await();//使当前线程在锁存器倒计数至零之前一直等待
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(Thread.currentThread().getId()+""+j, "test");
doneSignal.countDown();
}
}).start();
}
startSignal.countDown();//递减锁存器的计数,如果计数到达零,则释放所有等待的线程
try {
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("done,size:" + map.size());
}
}
上面的测试类里用到了一个jdk1.5里出现的类CountDownLatch,闭锁。因为我们要在所有线程结束后,来检验map的size。
测试结果:
done,size:948
可见,HashMap出了问题,1000个不重复的元素只放进去948个,如果加大N的值,程序就会陷入死循环。
解决方案是将HashMap换成ConcurrentHashMap,也是jdk1.5以后才有的类。它的并发性能要比Hashtable好。
使用N=7000,进行测试:
ConcurrentHashMap测试的最好结果是:
done,size:7000, time:1735
Hashtable的最好结果是:
done,size:7000, time:1800
如果N越大,效果会越明显。