2010-11-18 168 views
2

我有一个函数可以执行xml解析。我想使函数线程安全,但也尽可能优化(更少的阻塞)。
在短码的东西如下:关于多线程的java多线程

public Document doXML(InputStream s) 
{ 
//Some processing. 
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
    DocumentBuilder parser = factory.newDocumentBuilder(); 
    Document xmlDoc = parser.parse(is); 
    return xmlDoc; 

} 

但我不希望创建每次调用一个新的DocumentBuilderFactory或其的DocumentBuilder。
我想重用工厂和解析器,但我不确定它们是线程安全的。那么最优化的方法是什么?
1)在类字段中缓存DocumentBuilderFactory并同步factory.newDocumentBuilder();以便每个线程具有它自己的DocumentBuilder
实例 2)缓存一个的DocumentBuilderFactory 的DocumentBuilder和同步parser.parse(是);每个线程
我认为(2)是最好的,但它是安全的吗?我也可以通过同步避免阻塞?我希望它尽可能快。

谢谢?

+0

我不是很确定。在这个代码示例中,您不需要使用同步方法。由于没有共享对象来保护您的资源。如果你有任何共享的公共对象,你必须同步它们才能相互排斥。 – 2010-11-18 16:51:36

+0

@Mohamed Saligh:我想让DocumentBuilder和DocumentBuilderFactory共享。现在,你是对的没有问题 – Cratylus 2010-11-18 17:01:53

回答

4

如果您正在重用线程(如在线程池中),则可以将DocumentBuilderFactory声明为线程本地。为每个线程创建一个新的集合的开销,但正如我所说,如果你正在努力,后续的开销是非常低的。

final ThreadLocal<DocumentBuilderFactory> documentBuilderFactor = new ThreadLocal<DocumentBuilderFactory>(){ 
    public DocumentBuilderFactory initialValue(){ 
     return DocumentBuilderFactory.newInstance(); 
    } 
} 

public Document doXML(InputStream s) 
{ 
//Some processing. 
    DocumentBuilderFactory factory = documentBuilderFactor.get(); 
    DocumentBuilder parser = factory.newDocumentBuilder(); 
    Document xmlDoc = parser.parse(is); 
    return xmlDoc; 

} 

在这里,您将只为每个线程创建一个DocumentBuilderFactory。

我不知道DocumentBuilder在解析时是否线程安全(它是不可变的吗?)。但是,如果DocumentBuilder在解析时是线程安全的,则可以使用与我所述的相同的机制。

该解决方案将尽可能快地提高整体吞吐量。

注意:这未经测试或编译只是给出了我所指的内容。

+0

@John V.:我应该提供这个功能(以及其他)。线程将调用这个函数,但我无法控制这些线程是如何/何时创建的(不是我的部分)。所以我不知道线程是否会被重用。因此,如果线程不被重用,代码就相当于我的原始文章? – Cratylus 2010-11-18 17:33:15

+0

是的,你是对的。如果你不重复使用线程,那么在新的dom工厂创建时使用ThreadLocal的开销会有点高。然后再次启动新线程,每次我认为是一个更大的问题:) – 2010-11-18 17:36:15

+0

@John V .:你是对的!但如果他们是线程池和我使用线程本地,可能会有泄漏?看看这个:http://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-ide – Cratylus 2010-11-18 17:38:58

1

如果你想避免同步阻塞,你应该确保你使用原子操作。 javax.xml.parser.*的行为取决于实现(您可以使用系统属性指定实现,或者调用实现代码)。根据线程数量和每个线程的负载权重,控制解析器对象创建可能是合理的。您应该选择新的解析器创建或等待解析器。代码可以在启动时创建一个解析器池,然后线程从池中获取解析器,这会在没有可用解析器时阻塞。一旦线程获得解析器,它就解析数据,重置解析器并放回池中。您始终可以通过池的长度来控制时间/内存使用情况。

+0

@khachik:请你详细说明一下“确保你使用原子操作”。我不确定你的意思,以及如何做到这一点,以避免阻止 – Cratylus 2010-11-18 17:04:33

+0

@ user384706好吧,“原子”不是XML解析的最佳词汇,因为它可能会造成混淆(通常原子指的是可以原子操作的操作硬件层(Compare-and-Swap)第一句意思是说你不能在两个线程中使用同一个分析器而不阻塞其中的一个线程所以它应该确保解析器只在一个线程中使用 – khachik 2010-11-18 17:10:40

+0

@khachik:但我怎样才能确定池的大小?我的理解是最好的线程数是numberOfProcessors + 1。这是我的池的正确大小吗? – Cratylus 2010-11-18 20:27:23

2

2)将是线程安全的,但您的应用程序一次只能解析一个文档。

为什么不只是使用你的代码?

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
DocumentBuilder parser = factory.newDocumentBuilder(); 

有一个明显不可接受的开销?

+0

通过光测试,我没有发现问题。但我不确定是否会因负载而变差。从阅读中,我的理解是创建一个工厂太昂贵了。我认为这也代表了DocumentBuilder。所以我想知道这个代码是否会最终显示出糟糕的性能 – Cratylus 2010-11-18 17:08:36

+0

创建一个工厂确实有一些*开销,但与从流中读取和解析文档相比,这可能是微不足道的。大部分开销似乎是通过检查系统属性jaxb.properties(它只做一次并缓存)并最后在META-INF/services中找到实现类的名称。通过给它一个实现类名称可以减少开销。 – Qwerky 2010-11-19 11:35:40

+0

有趣。但是我怎么知道实现类?你提出了一些类似于John M的答案,对吧?我如何知道将在运行时使用的类名,我的代码将运行?你能向我解释一下吗? – Cratylus 2010-11-19 20:03:00

1

我在类似的情况下遇到了一些性能问题。我在每次使用时都创建了工厂对象以避免线程问题(每秒10次)。在那个(当然是老的)平台中的XML实现为服务提供者类做了一些相对较慢的查找逻辑。

我的调整是确定导致并通过命令行属性对其进行配置的答案。这导致查询被跳过。

-Djavax.xml.parsers.DocumentBuilderFactory=com.example.FactoryClassName 
-Djavax.xml.transform.TransformerFactory=com.example.OtherFactoryClassName 

令人沮丧的是,查找代码有缓存逻辑,如果找到一个类。但没有缓存未命中(没有发现,使用默认值)。稍微好一点的查找缓存处理负面情况会使这不必要。

这仍然需要吗?需要在您的环境中进行测试。我在Solaris上使用truss来注意由查找逻辑导致的非常频繁的文件操作。

+0

这个“窍门”仅适用于特定的平台,对吧?我怎么知道工厂的实际实现类是什么,在我的代码运行的机器上? – Cratylus 2010-11-18 20:26:03

+0

就我而言,我通过阅读源代码找出了默认答案。但第一步是确认你是否有这样的麻烦(查找经常出现+你经常关注)。除非你得到一些真正的好处,否则没有必要使用硬编码这样的东西。 – 2010-11-19 22:30:14

+0

你有关于对这个问题进行基准测试的建议,看看我是否需要像你一样的建议? – Cratylus 2010-11-22 10:11:19