Pregunta

A menudo necesito implementar DAO para algunos datos de referencia que no cambian muy a menudo. A veces guardo esto en el campo de recopilación en el DAO, de modo que solo se carga una vez y se actualiza explícitamente cuando es necesario.

Sin embargo, esto trae muchos problemas de concurrencia: ¿qué sucede si otro hilo intenta acceder a los datos mientras se está cargando o actualizando?

Obviamente, esto puede manejarse sincronizando tanto los captadores como los establecedores de los datos, pero para una aplicación web de gran tamaño, esto es una sobrecarga.

He incluido un ejemplo trivial imperfecto de lo que necesito como hombre de paja. Sugiera formas alternativas de implementar esto.

public class LocationDAOImpl implements LocationDAO {

private List<Location> locations = null;

public List<Location> getAllLocations() {
    if(locations == null) {
        loadAllLocations();
    }
    return locations;
}

Para obtener más información, estoy usando Hibernate y Spring, pero este requisito se aplicaría en muchas tecnologías.

Algunas ideas adicionales:

¿No debería manejarse esto en el código? Deje que ehcache o similar lo maneje. ¿Hay un patrón común para esto que me estoy perdiendo? Obviamente, esto se puede lograr de muchas maneras, pero nunca he encontrado un patrón que sea simple y fácil de mantener.

¡Gracias de antemano!

¿Fue útil?

Solución

Si solo desea una solución rápida de almacenamiento en caché de su propio rollo, eche un vistazo a esto artículo sobre JavaSpecialist, que es una revisión del libro Concurrencia de Java en la práctica por Brian Goetz .

Habla sobre la implementación de una caché básica segura para subprocesos utilizando un FutureTask y un ConcurrentHashMap .

La forma en que se hace esto asegura que solo un subproceso concurrente active el cálculo de larga duración (en su caso, su base de datos llama a su DAO).

Tendría que modificar esta solución para agregar caducidad de caché si la necesita.

El otro pensamiento sobre el almacenamiento en caché es la recolección de basura. Sin utilizar un WeakHashMap para su caché, entonces el GC no podría liberar la memoria utilizada por el caché si fuera necesario. Si está almacenando en caché datos a los que se accede con poca frecuencia (pero datos que todavía valía la pena almacenar en caché, ya que es difícil de calcular), es posible que desee ayudar al recolector de basura cuando se esté quedando sin memoria utilizando un WeakHashMap.

Otros consejos

La forma más simple y segura es incluir la biblioteca ehcache en su proyecto y usarla para configurar un caché Estas personas han resuelto todos los problemas que puede encontrar y han hecho que la biblioteca sea lo más rápida posible.

En situaciones en las que hice rodar mi propio caché de datos de referencia, generalmente usé un ReadWriteLock para reducir la contención de hilos. Cada uno de mis accesores luego toma la forma:

public PersistedUser getUser(String userName) throws MissingReferenceDataException {
    PersistedUser ret;

    rwLock.readLock().lock();
    try {
        ret = usersByName.get(userName);

        if (ret == null) {
            throw new MissingReferenceDataException(String.format("Invalid user name: %s.", userName));
        }
    } finally {
        rwLock.readLock().unlock();
    }

    return ret;
}

El único método para eliminar el bloqueo de escritura es refresh(), que normalmente expongo a través de un MBean:

public void refresh() {
    logger.info("Refreshing reference data.");
    rwLock.writeLock().lock();
    try {
        usersById.clear();
        usersByName.clear();

        // Refresh data from underlying data source.

    } finally {
        rwLock.writeLock().unlock();
    }
}

Por cierto, opté por implementar mi propio caché porque:

  • Mis colecciones de datos de referencia son pequeñas, por lo que siempre puedo almacenarlas todas en la memoria.
  • Mi aplicación debe ser simple / rápida; Quiero la menor cantidad posible de dependencias en bibliotecas externas.
  • Los datos rara vez se actualizan y cuando es la llamada a actualizar () es bastante rápido. Por lo tanto, inicializo ansiosamente mis cachés (a diferencia de su ejemplo de hombre de paja), lo que significa que los accesores nunca necesitan quitar el bloqueo de escritura.

Si sus datos de referencia son inmutables, el caché de segundo nivel de hibernación podría ser una solución razonable.

  

Obviamente, esto puede manejarse sincronizando tanto los captadores como los establecedores de datos, pero para una aplicación web de gran tamaño esto supone una sobrecarga.

     

He incluido un ejemplo trivial imperfecto de lo que necesito como hombre de paja. Sugiera formas alternativas de implementar esto.

Si bien esto puede ser cierto, debe tener en cuenta que el código de muestra que ha proporcionado ciertamente debe sincronizarse para evitar cualquier problema de concurrencia al cargar lentamente el locations. Si ese descriptor de acceso no está sincronizado, tendrá:

  • Varios hilos acceden al método loadAllLocations() al mismo tiempo
  • Algunos subprocesos pueden ingresar <=> incluso después de que otro subproceso haya completado el método y haya asignado el resultado a <=> - bajo el Modelo de Memoria Java no hay garantía de que otros subprocesos verán el cambio en la variable sin sincronización.

Tenga cuidado al usar la carga / inicialización diferida, parece un simple aumento del rendimiento, pero puede causar muchos problemas de subprocesos desagradables.

Creo que es mejor no hacerlo usted mismo, porque hacerlo bien es algo muy difícil. Usar EhCache u OSCache con Hibernate y Spring es una idea mucho mejor.

Además, hace que sus DAO tengan estado, lo que podría ser problemático. No debería tener ningún estado, además de los objetos de conexión, fábrica o plantilla que Spring gestiona para usted.

ACTUALIZACIÓN: si sus datos de referencia no son demasiado grandes y realmente nunca cambian, quizás un diseño alternativo sería crear enumeraciones y prescindir de la base de datos por completo. Sin caché, sin hibernación, sin preocupaciones. Quizás valga la pena considerar el punto de oxbow_lakes: quizás podría ser un sistema muy simple.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top