在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();
System.out.println("element " + elem + " character " + s);
if ((doc != null) & (s != null) & !(s.trim().equals("")))
doc.put(elem, s);
sbData = new StringBuffer();
System.out.println(" endElement ending element............." + qName);
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:\testareajava -classpath c:\testarea prodcons.TestProducerConsumerForXML c:\testarea\prodcons\order.xml
这篇文章用一些程序描述了解析xml文档元素的方法,也解释了一些关于生产和消费模式的概念,还有和线程的应用。