接口在压测的时候发现并发量上去之后,tps并没有按预期的上升,于是在开发环境下用jprofiler进行监控,发现大量线程处于阻塞状态
线程阻塞信息如下:
可以看到阻塞都是由于SynchronizedMap.get方法造成
由于dom4j内部缓存QNameCache使用的是Collections.synchronizedMap
package org.dom4j.tree;
import org.dom4j.DocumentFactory;
import org.dom4j.Namespace;
import org.dom4j.QName;
import java.util.*;
public class QNameCache {
protected Map<String, QName> noNamespaceCache = Collections.synchronizedMap(new WeakHashMap<String, QName>());
protected Map<Namespace, Map<String, QName>> namespaceCache = Collections.synchronizedMap(new WeakHashMap<Namespace, Map<String, QName>>());
public QNameCache(DocumentFactory documentFactory) {
this.documentFactory = documentFactory;
}
public QName get(String name) {
QName answer = null;
if (name != null) {
answer = noNamespaceCache.get(name);
} else {
name = "";
}
if (answer == null) {
answer = createQName(name);
answer.setDocumentFactory(documentFactory);
noNamespaceCache.put(name, answer);
}
return answer;
}
public QName get(String name, Namespace namespace) {
Map<String, QName> cache = getNamespaceCache(namespace);
QName answer = null;
if (name != null) {
answer = cache.get(name);
} else {
name = "";
}
if (answer == null) {
answer = createQName(name, namespace);
answer.setDocumentFactory(documentFactory);
cache.put(name, answer);
}
return answer;
}
}
解决方案: 因为项目的使用场景xml节点是固定的,不会无限增加,所以采用ConcurrentHashMap替换synchronizedMap,,如果节点数不固定且会无限增加,那么不能使用ConcurrentHashMap,有可能会导致缓存数据不会被回收,造成OOM
DocumentFactory初始化的方式是通过类加载加载具体类,具体类可以以系统的变量指定 ,key:org.dom4j.factory
import org.dom4j.DocumentFactory;
import org.dom4j.QName;
import org.dom4j.tree.QNameCache;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class CustomQNameCache extends QNameCache {
public CustomQNameCache() {
this(null);
}
public CustomQNameCache(DocumentFactory documentFactory) {
super(documentFactory);
noNamespaceCache = new ConcurrentHashMap();
namespaceCache = new ConcurrentHashMap();
}
}
import org.dom4j.DocumentFactory;
import org.dom4j.tree.QNameCache;
public class CustomDocumentFactory extends DocumentFactory {
@Override
protected QNameCache createQNameCache() {
return new CustomQNameCache(this);
}
}
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication()
@MapperScan("com.ttfund.trade.unpaid.dal.mapper")
@EnableCaching
@ServletComponentScan
public class WebApiApplication {
public static void main(String[] args) {
//dom4j解析时防止线程阻塞
System.setProperty("org.dom4j.factory","com.ttfund.trade.unpaid.utility.xml.CustomDocumentFactory");
SpringApplication.run(WebApiApplication.class, args);
}
}