在B2B(企業對企業)應用中XML扮演一個重要的角色。在這些應用中采用Simple API for XML (SAX)或者document.nbspObject Model (DOM)解析器來解析xml文件。(這兩個解析器都是java的api,他們可以在下面的附錄中找到)在一個單線程應用中解析是簡單明了的。但是,在多線程的應用中這就是很復雜和具有挑戰性了,比如說做一個應用服務器,因為應用經常會為解析xml創建一個專門的線程,解析的數據用來為許多同時並發運行的線程服務。這篇文章描述了一個在並發應用中的xml的解析實現。
設計方法
基於並發的生產和消費設計概念,一個專門的線程作為一個生產者去解析xml。一組線程作為消費者,作為解析xml數據的生產線程,他把數據存儲在一個共享的數據結構中以供消費線程在將來進行處理時取得,為了最大化產生數據的能力同時最小化內存的使用,這個設計使用了一個特別的隊列來分別為生產者、消費者存儲和找到解析的數據.
巧妙的隊列(Smart Queuing)
SmartQueue 隊列類提供給生產消費線程們隊列的功能,他主要的責任是維護隊列防止(線程)超載和斷流。換句話說,SmartQueue采用維護一個固定長度的隊列的方法去保持資源的應用效率。他掛起和喚醒適當的線程在適當的時候,打個比方,如果沒有填充數據的空間,隊列將掛起生產線程直到一個消費線程從隊列裡移去一項。
下面的SmartQueue 代碼片斷展示了這種策略的實現。
public synchronized void put(Object data) {
// check to see if the length is 2
while (list.size() >= 2) {
try {
System.out.println("Waiting to put data");
wait();
}
catch (Exception ex) {
}
}
list.add(data);
notifyAll();
}
public synchronized Object take() {
// wait until there is data to get
// come out if the end of file signaled
while (list.size() <= 0 && (eof != true)) {
try {
System.out.println("Waiting to consume data");
wait();
} catch (Exception ex) {
}
}
Object obj = null;
if (list.size() > 0) {
obj = list.remove(0);
} else {
System.out.println("Woke up because end of document.quot;);
}
notifyAll();
return obj;
}
xml 解析
這個設計使用SAX API來解析XML文件是有以下原因的:
這個API讀取 XML數據是快速高效的,他不構造任何內部的XML數據描述,相應的,他在遇到XML元素時簡單的把數據傳遞給應用程序。SAX API十分適合生產-消費模式。
xml 解析控制器(XMLParserHandler) 的類繼承自SAX,實現回叫(callback )方法從解析器中接收XML數據,當解析控制器類從解析器中接收XML數據時,他把數據put進hashtable裡。在每個文檔的結尾,解析控制器把數據put進SmartQueue隊列裡。這個控制器將進入一個等待狀態如果SmartQueue隊列裡有空間,一旦消費線程從SmartQueue隊列中移去一項,put方法將被調用。在完成整個XML文檔的解析後,解析控制器(
XMLParserHandler)通知消費線程停止搜索更多的文檔。
讓我們看看回叫(callback )方法,他把數據存儲入SmartQueue隊列然後通知等待的消費線程。起始元素(startElement)方法為每個XML文件中的每個文擋元素示例一個新的Hashtable。
public void startElement( String namespaceURI, String localName,
String qName, Attributes atts )
throws SAXException {
System.out.println(
" startElement local names............." +
localName + " " + qName);
if (qName.equalsIgnoreCase(elemmark)) {
doc = new Hashtable();
}
elem = qName;
}
結尾元素(endElement)方法負責把解析的數據加到SmartQueue隊列中。就象前面提起的,SmartQueue隊列掛起這個線程直到沒有空間來存儲數據。
public void endElement( String namespaceURI, String localName,String qName )
throws SAXException {
String s = sbData.toString();
if (qName.equalsIgnoreCase(elemmark)) {
System.out.println(
" endElement ending element............." + localName);
smartQueue.put(doc);
doc = null;
}
}
最終,結尾文檔元素(enddocument.nbsp)回叫方法通知消費線程到達了xml文檔的結尾。這意味著消費線程不用去等待其他數據完成他們的工作。
public void enddocument.) throws SAXException {
smartQueue.end();
System.out.println("End document.............");
}
消費線程
消費線程移從SmartQueue隊列中除項目一旦生產線程把項目放入SmartQueue隊列。如果SmartQueue隊列為空,每個消費線程將要進入等待狀態。消費線程會一直運行直到生產線程通知已經達到了文檔元素的結尾而且SmartQueue隊列中再沒有項目了。這裡有一個消費線程的例子實現,他保持不斷地從SmartQueue隊列中取數據直到隊列中沒有數據或者達到了文檔元素的末尾。
public void run() {
while (!queue.isEmpty() || !queue.onEnd()) {
Hashtable val = (Hashtable) queue.take();
System.out.println("Obtained by " + this.getName() + " " + val);
// try {
// System.out.println("Simulate lengthy
processing...........");
// Thread.sleep(2000);
// }
// catch(Exception ex){}
}
}
優點
這個設計有以下優點:
解析和數據消費可以並發的進行。大的xml文件只用很少的內存就能被解析
設計的擴展
SmartQueue隊列執行固定長度隊列的策略來維護內存的效率。改變它的T取(take)和存(put)方法,你能執行一個不同的策略。在前面提到的,xml解析控制器(XMLParserHandler)產生一個xml元素和值的hashtable。然而,這個類可以被定制去建立應用指定的對象。
例子程序
source.zip文件包括一個TestProducerConsumerForXML類可以把xml文件作為一個參數運行。根據下面的說明來運行程序:
Unzip the source.zip file.
Run the program TestProducerConsumerForXML with order.xml.
For example
c:\testarea>java -classpath \
c:\testarea prodcons.TestProducerConsumerForXML \
c:\testarea\prodcons\order.xml
這篇文章用一些程序描述了解析xml文檔元素的方法,也解釋了一些關於生產和消費模式的概念,還有和線程的應用。