2010-11-18 127 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