标签:
承认有些标题党味道,但却在实际异步框架中使用了。
比起“公认”concurrentHashMap方式,提高有3-4倍的性能以及更低cpu占有率
异步框架需要一个buffer,存放请求数据,多线程共享。
显然这是一个多线程并发问题。
开始小觑了问题,以为只是简单地锁住资源、插入请求对象,都是内存操作,时间短,即使“堵”也不严重。
private void multiThreadSyncLock(final int numofThread,final Map<String,String> map) throws Exception { final long[] errCount=new long[numofThread+1]; Thread t = new Thread(new Runnable() { public void run() { for (int i = 0; i < numofThread; i++) { new Thread(new Runnable() { public void run() { String val=UUID.randomUUID().toString(); String key=Thread.currentThread().getName(); int index=Integer.parseInt(key.substring(7, key.length()))+1; long t1=System.currentTimeMillis(); for(int j=0;j<10000;j++) { synchronized(map) {map.put(key,val);} //获得锁后插入 if(!(val).equals(map.get(key))) errCount[0]++; //errCount >1 表示读出数据和写入不同 } long t2=System.currentTimeMillis(); errCount[index]=+errCount[index]+t2-t1; } }, "Thread-" + i).start(); } } }, "Yhread-main"); t.start(); Thread.currentThread().sleep(1000); t.join(); long tt=0; for(int i=1;i<=numofThread;i++) tt=tt+errCount[i]; log.debug("numofThread={},10,000 per thread,total time spent={}",numofThread,tt); Assert.assertEquals(0,errCount[0]); }
结果惨不忍睹!而且随着并发线程数量增加,“堵”得严重
100并发,每线程申请插 入数据10000次,总耗时 |
200并发,每线程申请插 入数据10000次,总耗时 |
4567.3ms | 20423.95ms |
@Test public void multiThreadPutConcurrentHashMap100() throws Exception{ final Map<String,String> map1=new ConcurrentHashMap<String,String>(512); for(int i=0;i<100;i++) multiThreadPutMap(100,map1); } private void multiThreadPutMap(final int numofThread,final Map<String,String> map) throws Exception { final long[] errCount=new long[numofThread+1]; Thread t = new Thread(new Runnable() { public void run() { for (int i = 0; i < numofThread; i++) { new Thread(new Runnable() { public void run() { String val=UUID.randomUUID().toString(); String key=Thread.currentThread().getName(); int index=Integer.parseInt(key.substring(7, key.length()))+1; long t1=System.currentTimeMillis(); for(int j=0;j<10000;j++) { map.put(key,val); //map的实现concurrentHashMap和HashMap if(!(val).equals(map.get(key))) errCount[0]++; //errCount >1 表示读出数据和写入不同 } long t2=System.currentTimeMillis(); errCount[index]=+errCount[index]+t2-t1; } }, "Thread-" + i).start(); } } }, "Yhread-main"); t.start(); Thread.currentThread().sleep(1000); t.join(); long tt=0; for(int i=1;i<=numofThread;i++) tt=tt+errCount[i]; log.debug("numofThread={},10,000 per thread,total time spent={}",numofThread,tt); Assert.assertEquals(0,errCount[0]); }
使用concurrentHashMap 100并发,每线程申请插入 数据10000次,耗时 |
使用concurrentHashMap 200并发,每线程申请插入 数据10000次,耗时 |
使用concurrentHashMap 300并发,每线程申请插入 数据10000次,耗时 |
200.69ms | 402.36ms | 542.08ms |
对比同步锁,效率提高很多,约22-50倍,一个数量级的差距!
自旋锁,线程一直在跑,避免了堵塞、唤醒来回切换的开销,而且临界状态一条指令完成,大大提高了效率。
众所周知,hashmap数据结构是数组+链表(参考网上hashMap源码分析)。
简单来说,每次插入数据时:
当然有人会问
在回答前,先看看tomcat如何处理请求:请求达到后,tomcat会从线程池中,取出空闲线程,执行filter,最后servlet。
tomcat处理请求有以下特点:
所以,
问题1、3 显然是OK的
问题4,初始化hasmap时,预先分配一个较大的空间,可以避免扩容。比如对于300并发,new HashMap(512)。
请求虽然多,但是只有300个线程。
问题2,java对hash优化过,保证了hash的均匀度,避免重复。
public void checkHashcodeSpreadoutEnough() { int length=512; for(int j=0;j<10000;j++) { //重复1000次, Map<String,Object> map=new HashMap<String,Object>(length); for(int i=0;i<300;i++) { String key="Thread-"+(i+1); int hashcode=hash(key,length); Integer keyhashcode=new Integer(hashcode); log.debug("key={} hashcode={}",key,hashcode); if(map.containsKey(keyhashcode)) { //生成的hash值作为键保存在hash表,只要重复表示 有冲突 log.error("encounter collisions! key={} hashcode={}",key,hashcode); Assert.assertTrue("encounter collisions!", false); } } } } /* * 从hashMap源码提取的hash值计算方法 * 跟踪代码得知,一般情况下没有使用sun.misc.Hashing.stringHash32((String) k)计算 * 关于stringHash32,网上有评论,有兴趣可以查查。 */ private int hash(Object k,int length) { int h = 0; h ^= k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); h=h ^ (h >>> 7) ^ (h >>> 4); return h & (length-1); }
publiwuwu void multiThreadPutHashMap100() throws Exception{ final Map<String,String> map=new HashMap<String,String>(512); //更换map实现为hashMap for(int i=0;i<100;i++) multiThreadPutMap(100,map); } // multiThreadPutMap(100,map); 参见concurrentHashMap单元测试代码
使用HashMap 100并发,每线程申请插入 数据10000次,耗时 |
使用HashMap 200并发,每线程申请插入 数据10000次,耗时 |
使用HashMap 300并发,每线程申请插入 数据10000次,耗时 |
46.79ms | 99.42ms | 137.03 |
提高4.289164351倍 | 提高4.047073024倍 | 提高3.955922061倍 |
有近3-4倍的提升
另,
在i5-2.5G,8G win10 jdk1.7.0_17,64bit
测试数据, 没有考虑垃圾回收,数据有些波动。
hashMap默认填充因子为0.75,在构造方法中修改。
标签:
原文地址:http://www.cnblogs.com/31693884O/p/5695202.html