reflection并发问题介绍如何提高性能。Eclipse如何打开jdk源码呢?在JDK6中,reflection的并发性能并不好,在遇到在高并发环境下反射性能急剧下降的情况。大规模web应用就是有这么大并发呀,而且因为框架或其他原因,广泛使用reflection,所以值得重视这种问题。
建议打开JDK源代码。(在Eclipse中只需在Preferences->Installed JREs里把JRE设为你安装的JDK,而不是默认那个,然后Ctrl+Shift+T就可以用名字查找JDK的任意一个类了)
1. ClassLoader
看看Class.forName(String)这个方法,它在反射中使用之频繁,如大白菜一般。
Class.forName(String)只是调用了forName0(…)方法,这是个native方法,C++实现,看不到源代码。源代码
其实它会调用ClassLoader.loadClass(String),然后转到ClassLoader.loadClass(String, boolean)。源代码
在JDK6中,这是一个synchronized方法,这个方法做什么详见上面链接的源代码。它会对整个ClassLoader加锁,可想而知——整个系统都在这等待——扛不住高并发。
在JDK7中,这里得到了优化(感谢RednaxelaFX大大的提示)。源代码
该方法不再是synchronized,其做法是把整个方法体包在“synchronized (getClassLoadingLock(name))”中。这个name,是类的全限定名。它维护了一个map,key为类名, value为一个普通的Object(当做锁对象来用)。所以锁粒度一下子从ClassLoader类级降到了每个class的级别。听RednaxelaFX说JDK8还有进一步优化。
2. Proxy
Proxy技术在AOP中密切使用。广泛使用的有JDK Proxy和CgLib Proxy两种实现。
看一下JDK7的Proxy类:源代码
Proxy.getProxyClass(…)会转到Proxy.getProxyClass0(…)。然后一路往下看,出现了两次同步:
1) synchronized (loaderToCache) 源代码
这个loaderToCache存放了各个classLoader各自的class cache,key=classLoader,value=map<classLoader, map<className, class>>。
中间做的操作挺轻量的,但是锁粒度这么大,整个系统在这等待,扛不住高并发。
我建议两个改法:
第一个改法是去掉synchronized,把loaderToCache改成ConcurrentHashMap,目的不是减小同步范围,而是减小锁粒度,不对整个cache加锁,而只对map的一部分加锁(由于ConcurrentHashMap的实现),高并发时的性能会明显改善。
第二个改法是loaderToCache里面不直接存放每一个cache,而是把cache包装在一个简单的单元素容器里,对单个容器加锁,代码示例:
cacheContainer = loaderToCache.get(loader);
synchronized (cacheContainer) {
cache = cacheContainer.get()
if (cache == null) {
cache = new HashMap<>();
cacheContainer.set(cache);
}
}
2) synchronized (cache) 源代码
这个cache是class cache,key=className, value=class。
哎哟妈呀,也给整个cache加着锁哩!虽然锁范围很小,wait调用还会释放锁,但是你要让大家都为了一个类在这等待吗?
改法不多说了,与前面所述的类似。采用第二个改法稍微复杂点,因为当proxy generating失败时会remove相应的key,所以建议尽量不要remove吧,或者只对remove做同步。
另外还能看到Proxy.proxyClasses这个属性是个synchronized WeakHashMap,要是WeakHashMap也有个concurrent实现就好了。
JDK6 reflection的并发性能不是很高,JDK7有明显改善(JDK8进一步改善),况且JDK6离XP的命运不远了,大家还没升级的都赶紧升级吧(没错,就是你们这些大公司!)。至于Proxy,可以用CgLib的(字节码生成慢一些,但生成之后用起来就快多了)。
建议以后不要用synchronized Map了,用ConcurrentHashMap比较好,由于锁粒度的细化,高并发时的性能好很多。Spring框架就在2013年上半年把所有关于map的synchronization统统换成了ConcurrentHashMap。那时我本来想捡个漏赚个pull request,晚了一步啊。