Introduction
Dans les programmes où les actions sont rémanentes, nous avons besoin de gérer la logique plus intelligemment pour optimiser les performances.
Une de ces optimisations est de réduire le nombre de cycles cpu en réduisant le nombre d'instanciations d'objets.
En effet, la création d'objets est un processus coûteux.
Pour ce faire, nous pouvons utiliser des Pools qui contiendront les objets que nous avons créés auparavant et dont nous n'avons plus utilité.
Garbage Collector ou ramasse-miettes
En java, vous ne libérer pas la mémoire directement par vous même. Pour ce faire, la JVM (Java Virtual Machine) appelle le garbage collector (ramasse-miettes).
Il est appelé tout les X secondes et vérifie récursivement l'arbre d'objet pour obtenir les objets qui n'ont plus de références à un objet vivant (qui est référencé dans un thread).
Ainsi donc, quand le GC est appelé, les objets inutilisés seront libérés de la mémoire.
Comment implémenter le Pooling
L'idée du pooling est de réutiliser les objets inutilisés au lieu de les libérer et d'en recréer de nouveaux.
L'implémentation est assez simple et souple à mettre en place.
Tout d'abord, pour définir quels objets vont pouvoir être réutilisés, nous allons créer une interface Poolable.
public interface Poolable
{
public void free();
}
Après ça, nous avons besoin d'une classe globale qui gérera nos objets réutilisables. Elle est composée d'une collection de type Map permettant de lier un type d'objet au Pool le gérant.
La classe Pool est statique car elle ne sert que de passerelle entre les Pools.
public class Pools
{
private static Map<Class<?>, PoolType<? extends Poolable>> pools = new HashMap<>();
public static <T extends Poolable> T obtain(Class<T> type)
{
PoolType<?> _pool = pools.get(type);
if(pools.get(type) == null)
{
_pool = new PoolType<T>();
pools.put(type, _pool);
}
return _pool.obtain(type);
}
public static void free(Object object)
{
pools.get(object.getClass()).free(object);
}
}
Comme pour une classe singleton, quand obtain(Class) est appelé, une recherche de la classe est effectuée dans la collection de Pools pour savoir si ce type de Pool est déjà enregistré. Si la recherche échoue, un type de Pool (PoolType) est instancié et enregistré dans la collection.
public class PoolType<T extends Poolable>
{
private LinkedList<T> stack = new LinkedList<>();
public <T> T obtain(Class<T> type)
{
Object _object = null;
try {
_object = stack.getFirst();
stack.removeFirst();
System.out.println("Reuse object of type " + type.getSimpleName());
} catch (NoSuchElementException e)
{
try
{
_object = type.newInstance();
System.out.println("Instanciate new object of type " + type.getSimpleName());
} catch (InstantiationException | IllegalAccessException e1)
{
e1.printStackTrace();
}
}
return (T) _object;
}
public void free(Object object)
{
T _object = (T) object;
_object.free();
stack.add(_object);
}
}
Maintenant, la classe PoolType. Elle contient tout les objets d'un certain type où T est le type d'objet à stocker.
En utilisant <T extends Poolable> sur la classe, nous la définissons en tant que classe générique qui peux être utiliser pour plusieurs type d'objet héritant de Poolable.
La méthode obtain(Class) permet de récupérer un objet réutilisable dans la file d'attente ou en instancie un nouveau par réfection si la file d’attente est vide. Quand un objet doit être recyclé, il suffit d'appeler la méthode free(Object) qui va réinitialiser les valeurs de l'objet à celles par défauts et le mettre dans les fille d'attente des objets réutilisables.
Test
Pour tester tout ça, nous allons créer une nouvelle classe qui servira d'objet réutilisable.
public class PoolableObject implements Poolable
{
private int value;
private String valueStr;
public PoolableObject()
{
}
@Override
public void free()
{
value = 0;
valueStr = null;
}
public int getValue()
{
return value;
}
public void setValue(int value)
{
this.value = value;
}
public String getValueStr()
{
return valueStr;
}
public void setValueStr(String valueStr)
{
this.valueStr = valueStr;
}
}
Ici, notre classe réécrit la méthode free() de l'interface Poolable permettant de définir comment l'objet réutilisable doit être réinitialisé.
Lancez ce code
public class Main
{
public static void main(String[] args)
{
for(int i = 0; i < 5; i++)
{
PoolableObject _poolObject = Pools.obtain(PoolableObject.class);
Pools.free(_poolObject);
}
}
}
La sortie console :
Instanciate new object of type PoolableObject
Reuse object of type PoolableObject
Reuse object of type PoolableObject
Reuse object of type PoolableObject
Reuse object of type PoolableObject
Conclusion
Cette méthode est très utile mais doit être manié avec précautions.
Avantages :
- Performances améliorés
Inconvénients :
- Augmentation de la mémoire utilisé
- Fuite de mémoire si les objets ne sont pas libérés
- Erreurs si les objets ne sont pas réinitialisés correctement