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:

Patron Object Pool

Entidades involucradas:

  1. Reusable : Identifica los objetos o recursos costosos que son reusados.
  2. ReusablePool : Administra los recurso usados por los clientes.
  3. 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.

  1. Hay disponible y se le entrega al cliente.
  2. 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

  1. Manejar el almacenamiento de objetos genéricos.
  2. Expiración de los objetos.
  3. Podría tener Listeners para seguimiento(No implementada aquí)

Las características específicas de la clase que extiende del pool

  1. Creación de los Reusables específicos.
  2. Validación específica de los Reusables.
  3. 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

  1. Conexiones a base de datos
  2. Conexiones con Sockets
  3. Aplicaciones de interfaz gráfica
  4. 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.


Franky Villadiego

Volando hacia el desarrollo productivo!