XML TeX MathML
Темы:
XML как есть
Технология XSLT
Схемы данных
Программирование
Базы данных
Инструменты
Веб-сервисы
Мир стандартов
Приложения
code here глагол . Оборудованная стоматология Киев прием круглосуточно . купить диван в интернет магазине
Статьи

Кеширование преобразований, часть 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.
 */
О Raleigh О CompuTel Реклама на сайте Контакты
Рейтинг@Mail.ruliveinternet.ru