Al instanciar ciertos tipos de objetos estos realizan otras operaciones de inicialización que toman un poco más tiempo de lo habitual. Más aún, estos objetos son usados de forma periódica. Qué hacemos para evitar que esta situación degrade el rendimiento de nuestra aplicación?.
Creamos varios objetos y los colocamos en un sitio común de donde serán tomados para usarse y luego se devuelven una vez termine su uso. Como una biblioteca, tomas un libro, lo usas y lo regresas luego para ser usado por alguien más.
Estamos hablando de una piscina de objetos conocida como object pool.
Un object pool puede ser muy efectivo para ayudar al rendimiento, identificando aquellos objetos que sean como recursos, costosos de crear y usados intensivamente.
Este no es un patrón de GoF, pero por eso no deja de ser uno. Esta dentro de los patrones creacionales.
El diagrama de clases:
Entidades involucradas:
- Reusable : Identifica los objetos o recursos costosos que son reusados.
- ReusablePool : Administra los recurso usados por los clientes.
- Client : Usa las instancias de Reusable.
El ReusablePool es el sitio común que administra el uso coherente de los recursos. Este sitio común es nuestro pool o piscina, y es un Singleton. Ya he hablado del Singleton aquí.
Comportamiento
Los detalles de como funciona un pool pueden ser desde los más sencillos hasta los más complejos según las características
que necesitemos. Límite mínimo y máximo para objetos Reusable, tiempo de expiración y limpieza periódica, listeners para
seguimiento(tracking), etc.
Detalles de implementación
EAGER : Se crean todos los Reusables al iniciar la aplicación.
LAZY : Los Reusables se crean con el primer pedido de un cliente.
EAGER_LAZY : Se crea un minimo al cargar la aplicación y después con peticiones se van creando más Reusables.
Estrategia de adquisición
Cuando un cliente pide un Reusable varias situaciones se presentan.
- Hay disponible y se le entrega al cliente.
- No hay disponible: 2.1. Límite máximo no alcanzado, se crea un nuevo Reusable. 2.2. Límite máximo alcanzado, bloquea hasta que haya disponible. 2.3. Límite máximo alcanzado, lanza un error.
El Client
es responsable de hacer el pedido y regresar el Reusable
.
Ejemplo
Haremos un ejemplo muy básico de un pool. Creamos una clase abstracta que sirva de base con el comportamiento genérico. Luego la extendemos y agregamos comportamiento específico según el tipo de pool.
Las características genéricas de esta clase serán
- Manejar el almacenamiento de objetos genéricos.
- Expiración de los objetos.
- Podría tener Listeners para seguimiento(No implementada aquí)
Las características específicas de la clase que extiende del pool
- Creación de los Reusables específicos.
- Validación específica de los Reusables.
- Destrucción de los Reusables.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public abstract class ObjectPool {
private long expirationTime;
private HashMap<T, Long> locked, unlocked;
public ObjectPool() {
expirationTime = 60000; // 60 segundos
locked = new HashMap<T, Long>();
unlocked = new HashMap<T, Long>();
}
public synchronized T acquire() {
long now = System.currentTimeMillis();
T t;
if (unlocked.size() > 0) {
Iterator it = unlocked.keySet().iterator();
while (it.hasNext()) {
t = it.next();
//Verifcamos tiempo de expiracion
if ((now - unlocked.get(t)) > expirationTime) {
//Expiró, removemos
unlocked.remove(t);
expire(t); //Delegamos expiracion a una subclase
t = null;
} else {
if (validate(t)) {
unlocked.remove(t);
locked.put(t, now);
return (t);
} else {
// Si falla vaidacion, removemos y expiramos
unlocked.remove(t);
expire(t);
t = null;
}
}
}
}
// Si llega aqui es que no hay disponible y creamos uno.
t = create();
locked.put(t, now);
return (t);
}
public synchronized void release(T t) {
locked.remove(t);
unlocked.put(t, System.currentTimeMillis());
}
//Implementacaión especifica por una Subclase
protected abstract T create();
public abstract boolean validate(T o);
public abstract void expire(T o);
}
public class Reusable {
public void doSomeWork(){
//...
}
}
public class ReusablePool extends ObjectPool {
private static ReusablePool instance = new ReusablePool();
private ReusablePool(){
//Parametros de inicializacion
System.out.println("Iniciando pool");
}
public static ReusablePool getInstance(){
return instance;
}
protected Reusable create() {
//Logica para crear objetos Reusable
return new Reusable();
}
public boolean validate(Reusable o) {
//Validaciones especificas sobre Reusable
return true;
}
public void expire(Reusable o) {
//Destruir el objeto reusable
//Ej: cerrar si fuera una conexion
}
}
public class Principal {
public static void main(String[] args) {
//Obtenemos el Reusable del pool
Reusable reusable = ReusablePool.getInstance().acquire();
//Se usa
reusable.doSomeWork();
//Se libera
ReusablePool.getInstance().release(reusable);
}
}
Usos mas comunes
- Conexiones a base de datos
- Conexiones con Sockets
- Aplicaciones de interfaz gráfica
- Threads
Hoy dia ya no es tan necesario por ejemplo crear un pool de conexiones ya que existen varias librerías conocidas que te ayudan en eso. C3P0, apache DBCP, BoneCP. Incluso los servidores de aplicaciones traen sus propias implementaciones de pool jdbc.
A modo de práctica podrías tratar de crear uno siguiendo la estructura anterior.