Pool de objetos (II)
martes, enero 6, 2004 at 1:00AM Construye un pool de objetos en Java. Parte 2
Mejorando la robustez y el rendimiento de tu pool de objetos
Fecha de creación: 09.04.2003
Revisión 1.0 (09.04.2003)
Miguel Orbegozo (Miki)
mikiorbe@yahoo.com
|
Introducción
Va más allá de incrementar la velocidad de la aplicación y reducir sus requerimientos de memoria: optimiza y estabiliza nuestra utilidad de pool de objetos.
Mejorando la robustez y el rendimiento de tu pool de objetos
El pool de objetos es un modo sencillo y elegante de conservar memoria y aumentar velocidad. Compartiendo y reutilizando objetos, los procesos (hilos) no se ven penalizados por el tiempo de instanciación e inicialización de nuevos objetos, o por el exceso de recolección de basura.
En nuestro primer artículo sobre el pool de objetos [2], implementé un pool de conexiones a base de datos. En este artículo, ampliaré ese ejemplo, añadiendo propagación de excepciones y un hilo de limpieza.
Propagación de excepciones
Para hacer el pool de objetos lo más transparente posible, los métodos obtener y/o devolver deben lanzar la misma excepción/es que el constructor del objeto deseado. La propagación de esas excepciones es una manera simple de conseguirlo.
Empezando desde abajo, la fuente de cualquier excepción estará en la implementación del método crear del pool de objetos. Por tanto, nosotros simplemente añadiremos la cláusula throws Exception a su declaración.
Abstract Object crear() throws Exception;
En la implementación del método crear en JDBCConnectionPool, se hace una única llamada a Drivermanager.getConnection(). Ésta puede lanzar una SQLException. En lugar de capturar la excepción como hicimos en el primer artículo, la propagaremos.
Object crear() throws SQLException {
return (DriverManager.getConnection(_jdbc:mysql://_ + servidor +
_:3306/_ + bd + _?user=_ + usuario + _;password=_ + contraseña));
}
El pool de objetos llama al método crear() desde checkout(). Como crear() ahora lanza una excepción, el método checkOut() debe tratarla o pasarla. Nosotros haremos esto último, simplemente añadiendo throws Exception a la declaración de checkOut().
Synchronized Object checkOut() throws Exception
Ahora que la excepción ha alcanzado el método getConnection() de JDBCConnectionPool, éste la devuelve a SQLException (mediante casting) y la pasa al proceso que pidió la conexión a la base de datos.
Public Connection getConnection() throws SQLException {
try {
return ((Connection) super.checkout());
} catch (Exception e) {
throw ((SQLException) e);
}
}
De esta forma ahora los siguientes fragmentos de código son prácticamente intercambiables, porque ambos devuelven el mismo objeto y lanzan la misma excepción:
Class.forName(...).newInstance();
Connection c = DriverManager.getConnection(_);
JDBCConnectionPool cp = new JDBCConnectionPool(_);
Connection c = cp.getConnection(_);
El hilo de limpieza
Como se indicó en el artículo previo, existe una condición en la que el pool puede acumular un gran número de objetos que nunca son limpiados. En el origen de este problema se encuentra la ineficiencia de tener la lógica de expiración en el método checkOut(). Por tanto mataremos dos pájaros de un tiro (un hilo en este caso). Empezaremos trasladando la lógica de expiración a un método separado denominado cleanUp(). Entonces escribiremos un pequeño hilo que llame a este método periódicamente.
Nuestro hilo necesita solamente una referencia de vuelta al pool de conexiones y un periodo de latencia determinado (que se pasa en el constructor).
Class CleanUpThread extends Thread {
private ObjectPool pool;
private long sleepTime;
CleanUpThread(ObjectPool pool, long sleepTime) {
this.pool = pool;
this.sleepTime = sleepTime;
}
public void run() {
while (true) {
try {
sleep(sleepTime);
} catch (InterruptedException e) {
// ignorarla
}
pool.cleanup();
}
}
}
El hilo se instancia y se arranca en el constructor del pool de objetos.
cleaner = new CleanUpThread(this, expirationTime);
cleaner.start();
El Nuevo método cleanUp() que es llamado cada expirationTime milisegundos, simplemente libera todos los objetos antiguos en el pool y pide al recolector de basura que los reclame.
synchronized void cleanUp() {
Object o;
long now = System.currentTimeMillis();
Enumeration e = nobloqueados.keys();
while(e.hasMoreElements()) {
o = e.nextElement();
if ((now - ((Long) nobloqueados.get(o)).longValue()) > expirationTime) {
unlocked.remove( o );
expire( o );
o = null;
}
}
System.gc();
}
Accede al código completo de ObjectPool [3] para ver el método checkOut() sin la lógica de expiración en él. También se puede acceder al código completo de JDBCConnectionPool [3].
Respuestas a comentarios de los lectores
Varios lectores han planteado preguntas tras leer la primera parte del artículo sobre pool de objetos. A continuación se indican algunas de las cuestiones más relevantes.
¿Cómo sé cuándo un pool de objetos será beneficioso para mi situación?
Si tienes varios hilos corriendo en paralelo y crean, usan y desechan frecuentemente un objeto común, debes considerar la posibilidad de utilizar un pool para dicho objeto, especialmente si la inicialización o recolección de basura de dicho objeto es una operación costosa. Conexiones a base de datos y sockets son grandes ejemplos.
¿Qué puedo hacer con los procesos que piden objetos y nunca los devuelven?
Esto es una espada de doble filo. En algún punto, tienes que dejar la responsabilidad en las manos del proceso que utiliza el pool; no puedes hacer el pool completamente anti torpes...al menos, todavía no.
Si un proceso pide un objeto y sencillamente nunca lo devuelve, no hay nada que el pool pueda hacer al respecto. Mientras que el proceso tenga una referencia del objeto, la puede usar en cualquier momento; cualquier interferencia por parte del pool podría ser desastrosa.
La próxima versión mayor del JDK de Sun incluirá objetos de consulta. Estos permitirán al pool detectar (vía alguna clase envoltorio) cuándo un objeto pedido ha sido _congelado_ por un proceso, de forma que el pool pueda reclamarlo. Como una solución provisional, si el pool detecta que un objeto permanece pedido durante un periodo anormalmente largo de tiempo (definido por ti, el programador), el pool puede sencillamente liberar la referencia a ese objeto. Entonces, cuando el proceso libere su referencia, el objeto será liberado por el recolector de basura.
¿Cómo puedo limitar el tamaño del pool?
Yo no recomiendo en absoluto limitar el tamaño del pool. Además de ir en contra del propósito general del pool, limitar su tamaño puede abrir las puertas a pérdidas en el rendimiento y a la posibilidad de cuelgues desagradables. Aun así, si realmente quieres, puedes poner la lógica para asegurarte que el pool no supera el tamaño deseado en checkOut(). Después, también en checkOut(), tienes que utilizar los métodos wait() y notify() de Thread para asegurarte de que una llamada al pool espere hasta que haya un objeto disponible.
Conclusión
Con la propagación de excepciones y el hilo de limpieza, la clase ObjectPool se hace más robusta y se acerca más a ser completamente transparente. Deseo que esta herramienta sea tan útil para ti como lo ha sido para mí. Como siempre, agradeceré tus comentarios, críticas y sugerencias para un mejor diseño.
Recursos
[1] Artículo original,
http://www.javaworld.com/jw-08-1998/jw-08-object-pool.html
[2] Primera parte del artículo,
/articles.article.action?id=59
[3]
Código fuente de ObjectPool
[4]
Código fuente de JDBCConnectionPool
Acerca del autor
Miguel Orbegozo (Miki)
Miguel trabaja como AP en una gran empresa de integración, en temas sobre todo relacionados con Java (servidor, GUIs, etc.). Su tiempo libre le gusta pasarlo con su mujer, jugando, toqueteando el ordenador, etc.
Acerca del artículo original
Thomas E. Davis
Thomas E. Davis es un Programador Java Certificado por Sun. Aunque esté entretenido con Máquinas Linux y código Perl (y se pierda en ellos bastante), actualmente paga sus facturas desarrollando servicios de respaldo en Java sobre Windows NT.
otro 
Reader Comments