`
378629846
  • 浏览: 212917 次
  • 性别: Icon_minigender_1
  • 来自: 哈尔滨
社区版块
存档分类
最新评论

用闭锁测试HashMap的并发写入问题

    博客分类:
  • java
阅读更多

今天无意中看到以前写的代码,是一个单例的工厂模式实现,代码片段如下:

 

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越大,效果会越明显。

 

2
6
分享到:
评论
7 楼 378629846 2012-08-29  
378629846 写道
wangdf_jee 写道
N=10000时:Collections.synchronizedMap(new HashMap<String,String>()) 时间是8295ms
而 new ConcurrentHashMap<String,String>() 是8760ms.


N=1000时:Collections.synchronizedMap(new HashMap<String,String>()) 时间是190ms
而 new ConcurrentHashMap<String,String>() 是195ms.



这个没用过,回头可以试试!


在我的电脑上效果不如ConcurrentHashMap:

done,size:7000, time:1821

看了看源码,Collections.synchronizedMap()只是在HashMap的基础上增加了synchronized,代码如下:
public V put(K key, V value) {
       synchronized(mutex) {return m.put(key, value);}
}
从实现看并发性能应该和Hashtable差不多。而ConcurrentHashMap用的是分离锁,默认实现是使用了一个包含16个锁的Array,每个锁都守护Hash Bucket的1/16,这样就把锁的竞争减少到原来的1/16;见如下代码:
public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        return segmentFor(hash).put(key, hash, value, false);
    }
final Segment<K,V> segmentFor(int hash) {
        return (Segment<K,V>) segments[(hash >>> segmentShift) & segmentMask];
    }
还有,需要注意的是,ConcurrentHashMap为了提高性能和吞吐量,相对弱化了一些需要对整个map进行操作的方法,如:size和isEmpty,这时的size可能仅是个估算值。用的时候需要进行取舍
6 楼 378629846 2012-08-29  
hekuilove 写道
lz知道Collections.synchronizedMap吗

听说过,没用过,有时间可以研究一下。
5 楼 hekuilove 2012-08-28  
lz知道Collections.synchronizedMap吗
4 楼 378629846 2012-08-28  
wangdf_jee 写道
N=10000时:Collections.synchronizedMap(new HashMap<String,String>()) 时间是8295ms
而 new ConcurrentHashMap<String,String>() 是8760ms.


N=1000时:Collections.synchronizedMap(new HashMap<String,String>()) 时间是190ms
而 new ConcurrentHashMap<String,String>() 是195ms.



这个没用过,回头可以试试!
3 楼 wangdf_jee 2012-08-28  
N=10000时:Collections.synchronizedMap(new HashMap<String,String>()) 时间是8295ms
而 new ConcurrentHashMap<String,String>() 是8760ms.


N=1000时:Collections.synchronizedMap(new HashMap<String,String>()) 时间是190ms
而 new ConcurrentHashMap<String,String>() 是195ms.
2 楼 LvQing 2012-08-28  
很好,学习了 一下 CountDownLatch  倒数计数锁
1 楼 zjhlht 2012-08-28  
支持一下好的总结,好的重构精神

相关推荐

Global site tag (gtag.js) - Google Analytics