Статьи |
Кеширование преобразований, часть 2
Автор: Алексей Валиков, 30 января 2003
Страницы: 1![]() ![]() |
Как говорится, продолжение следует. Я недавно обновил реализацию кэширующей фабрики преобразований, о которой недавно писалось. Изменения небольшие, но довольно весомые для многопользовательских сред.
Дело в том, что если фабрика используется в многопоточной среде (например, на web-сервере), доступ к кешу преобразований могут получить сразу несколько потоков. Причем если одни из них будут читать (просто получать кешированные преобразования), то другие могут и писать (сохранять в кеш преобразования, ранее там отсутствовавшие). Возникает проблема синхронизации.
Эта проблема решалась в прошлой реализации, что называется, в лоб. Метод newTransformer был просто объявлен synchronized. Просто, но неэффективно. Неэффективно потому, что операция сохранения преобразования в кеш выполняется на порядки реже операции чтения из кеша. Но выходит, что кеш блокируется не только при записи, но и при чтении. То есть, и "читатели" будут вынуждены ожидать друг друга, хотя это совсем необязательно - HashMap, использующийся в качестве контейнера хеша параллельное чтение вполне допускает.
Таким образом, гораздо эффективнее было бы, если бы читатели не блокировали бы друг друга, а писатели - блокировали и читателей и других писателей.
Это - классическая задача из курса "Системы реального времени". Реализация на языке Java основана на использовании двух мониторов readersLock и writersLock и счетчика читателей readersCount.
Обновленный код представлен ниже:
package de.fzi.dbs.xml.transform;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.TransformerFactoryImpl;
import org.apache.log4j.Logger;
/**
* Caching implementation of JAXP transformer factory.
* This implementation caches templates that were loaded from
* local files so that consequent calls to local stylesheets
* require stylesheet reparsing only if stylesheet was changed.
*
* The easiest way to use this factory in yyour application is
* to create file named java.xml.transform.TransformerFactory
* containing only the name of this class in your
* WEB-INF/classes/META-INF/services directory. This will force
* JAXP to use transformer factory that you specify in this file.
*/
public class CachingTransformerFactory
extends TransformerFactoryImpl {
/** Map to hold templates cache. */
private static Map templatesCache = new HashMap();
/** Count of reading processes. */
private static int readersCount;
/** Readers lock. */
private static Object readersLock = new Object();
/** Writer lock. */
private static Object writerLock = new Object();
/** Factory logger. */
protected static Logger logger =
Logger.getLogger(CachingTransformerFactory.class);
/**
* Process the source into a Transformer object. If source is
* a StreamSource with systemID pointing to a file,
* transformer is produced from a cached templates object. Cache
* is done in soft references; cached objects are reloaded, when
* file's date of last modification changes.
* @param source An object that holds a URI, input stream, etc.
* @return A Transformer object that may be used to perform a
* transformation in a single thread, never null.
* @throws TransformerConfigurationException - May throw this
* during the parse when it is constructing the Templates object
* and fails.
*/
public Transformer newTransformer(Source source)
throws TransformerConfigurationException {
// Check that source in a StreamSource
if (source instanceof StreamSource)
try {
// Check that source is a file
final URI uri = new URI(source.getSystemId());
if ("file".equalsIgnoreCase(uri.getScheme()))
return newTransformer(new File(uri));
} catch (URISyntaxException urise) {
throw new TransformerConfigurationException(urise);
}
return super.newTransformer(source);
}
/**
* Creates a transformer from a file (and caches templates)
* or from cached templates object.
* @param file file to load transformer from.
*/
protected Transformer newTransformer(File file)
throws TransformerConfigurationException {
TemplatesCacheEntry templatesCacheEntry = null;
// Lock readers lock and increase readers count
synchronized (readersLock) {
readersCount++;
}
// Read
// Search the cache for the templates entry
templatesCacheEntry =
(TemplatesCacheEntry) templatesCache.get(
file.getAbsolutePath());
// Lock readers lock and decrease reades count
synchronized (readersLock) {
readersCount--;
}
// If entry found
if (templatesCacheEntry != null) {
// Check timestamp of modification
if (templatesCacheEntry.lastModified
< templatesCacheEntry.templatesFile.lastModified())
templatesCacheEntry = null;
}
// If no templatesEntry is found or this entry was obsolete
if (templatesCacheEntry == null) {
logger.debug("Loading transformation [" +
file.getAbsolutePath() + "].");
// If this file does not exists, throw the exception
if (!file.exists()) {
throw new TransformerConfigurationException(
"Requested transformation ["
+ file.getAbsolutePath()
+ "] does not exist.");
}
// Create new cache entry
templatesCacheEntry =
new TemplatesCacheEntry(newTemplates(
new StreamSource(file)), file);
// Save entry to the cache
boolean succeeded = false;
while (!succeeded) {
// Lock readers
synchronized (readersLock)
{
// If there are no readers, proceed with writing
if (readersCount == 0)
// Lock writers
synchronized (writerLock) {
// Write
// Save this entry to the cache
templatesCache.put(file.getAbsolutePath(),
templatesCacheEntry);
// Indicate that writing succeeded
succeeded = true;
}
}
// If not succeeded (there were writers), wait a little bit
if (!succeeded)
Thread.currentThread().yield();
}
} else {
logger.debug("Using cached transformation [" +
file.getAbsolutePath() + "].");
}
return templatesCacheEntry.templates.newTransformer();
}
/**
* Private class to hold templates cache entry.
*/
private class TemplatesCacheEntry {
/** When was the cached entry last modified. */
private long lastModified;
/** Cached templates object. */
private Templates templates;
/** Templates file object. */
private File templatesFile;
/**
* Constructs a new cache entry.
* @param templates templates to cache.
* @param templatesFile file, from which this
* transformer was loaded.
*/
private TemplatesCacheEntry(Templates templates,
File templatesFile) {
this.templates = templates;
this.templatesFile = templatesFile;
this.lastModified = templatesFile.lastModified();
}
}
}
/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a
* copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
* or implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is: all this file.
*
* The Initial Developer of the Original Code is Aleksei Valikov
* of Forschungszentrum Informatik (valikov@fzi.de).
*
* Portions created by (your name) are Copyright (C)
* (your legal entity).
* All Rights Reserved.
*
* Contributor(s): none.
*/



