哪种实现更好:基于WeakHashMap的缓存还是基于ThreadLocal的缓存?

时间:2010-06-29 10:47:19

标签: java performance multithreading caching

我很难在以下两个实现之间做出决定。 我想缓存每个线程的javax.xml.parsers.DocumentBuilder对象。我主要担心的是运行时性能--Hench我很乐意避免使用尽可能多的GC。记忆不是问题。

我已经写了两个POC实现,很高兴听到社群PROS / CONS关于每个实现。

感谢帮助人员。

选项#1 - WeakHashMap

import java.io.IOException;
import java.io.StringReader;
import java.util.WeakHashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;


public class DocumentBuilder_WeakHashMap {
    private static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    private static final WeakHashMap<Thread, DocumentBuilder> CACHE = new WeakHashMap<Thread, DocumentBuilder>();

    public static Document documentFromXMLString(String xml) throws SAXException, IOException, ParserConfigurationException {
        DocumentBuilder builder = CACHE.get(Thread.currentThread());
        if(builder == null) {
            builder = factory.newDocumentBuilder();
            CACHE.put(Thread.currentThread(), builder);
        }

        return builder.parse(new InputSource(new StringReader(xml)));
    }

}

选项#2 - ThreadLocal

import java.io.IOException;
import java.io.StringReader;
import java.lang.ref.WeakReference;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;


public class DocumentBuilder_ThreadLocal {
    private static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    private static final ThreadLocal<WeakReference<DocumentBuilder>> CACHE = 
        new ThreadLocal<WeakReference<DocumentBuilder>>() {
            @Override 
            protected WeakReference<DocumentBuilder> initialValue() {
                try {
                    return new WeakReference<DocumentBuilder>(factory.newDocumentBuilder());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };

    public static Document documentFromXMLString(String xml) throws ParserConfigurationException, SAXException, IOException {
        WeakReference<DocumentBuilder> builderWeakReference = CACHE.get();
        DocumentBuilder builder = builderWeakReference.get();

        if(builder == null) {
            builder = factory.newDocumentBuilder();
            CACHE.set(new WeakReference<DocumentBuilder>(builder));
        }

        return builder.parse(new InputSource(new StringReader(xml)));
    }
}

他们都做同样的事情(将documentFromXMLString()暴露给外界),你会使用哪一个?

谢谢你, 格言。

3 个答案:

答案 0 :(得分:6)

只要您不使用弱引用,而是直接使用ThreadLocal<DocumentBuilder>,ThreadLocal解决方案就更好了。 对ThreadLocal值的访问更快,因为线程直接引用包含所有ThreadLocal值的数组,并且它只需要计算此数组中的索引以进行查找。 查看ThreadLocal source以了解索引计算速度快的原因(int index = hash & values.mask;

答案 1 :(得分:4)

小心!

ThreadLocal将保留对DocumentBuilder的无限引用,其中包含对该线程DocumentBuilder解析的最新XML文档的引用。

这有几个后果,可能被认为是内存泄漏:

  • 如果在Web应用程序中加载JAXP实现(例如,Xerces或Oracle的xmlparser2.jar),则保留对DocumentBuilder的引用将导致Web应用程序的所有类在取消部署时泄漏,最终导致OutOfMemoryError:PermGenSpace! (谷歌周围有关此主题的更多信息)
  • 如果由DocumentBuilder解析的最新XML文档很大,它将继续占用内存,直到在该线程上解析新的XML文档。如果在线程池中有长时间运行的线程(例如在J2EE容器中),这可能是一个问题,尤其是在需要解析大量大型文档时。是的,最终将释放内存,但在此之前可能会耗尽可用内存,并且GC在DocumentBuilder引用时将无法清理XML文档存在。

决定这是否与您相关......

答案 2 :(得分:3)

单独的WeakHashMap将失败,因为它不是线程安全的:
“与大多数集合类一样,此类不同步。”
JavaDoc)的第3段

由于同步需要花费时间而Collections.synchronizedMap无法很好地扩展,因此您应该坚持使用ThreadLocal