Object pool

La programmation avec design patterns

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
La création de certains objects nécessite beaucoup de ressources de la machine. Cela peut provoquer des lenteurs pour d'autres fonctionnalités, mais aussi des dysfonctionnements plus graves, comme par exemple l'arrêt de fonctionnement total. Pour résoudre ce problème, un design pattern vient au secours - object pool.

Le design pattern object pool (réservoir d'objets) est une sorte de réservoir qui contient beaucoup d'objects, souvent nécessitant de temps d'initialisation important, réutilisables à la demande. L'application, à la place de créer à chaque fois l'instance d'une classe, recherche d'abord si une instance est disponible à être réutilisée. Si aucune instance est disponible, deux choix s'imposent : soit une nouvelle instance est créée et rajoutée dans le réservoir, soit l'application cesse de fonctionner. Cette deuxième solution est une bonne illustration de ce à quoi peut servir object pool.

L'usage primaire, à part économiser des ressources lors de la création d'un objet, peut être le contrôle du nombre d'instances vivant dans la mémoire. Cela peut être très utile dans les applications dont ce paramètre est très important, comme par exemple des applications qui se connectent à un service à distance. On peut imaginer le cas d'un gestionnaire des connexions à une base de données. Au démarrage de l'application on initialise le pool avec 20 objets représentant la connexion. Ensuite les 20 premiers utilisateurs prennent chacun une connexion disponible. Celui qui la relâche, remet un objet dans le réservoir. Grâce à cette gestion, on peut s'assurer qu'un nombre trop important des connexion ne parviendra pas à la machine et que l'application ne s'arrêtera pas brusquement.

En mettant en place ce patron de conception, il faut bien synchroniser l'accès à des objets. Il faut également s'assurer que les objets seront toujours relâchés : soit d'une manière explicite par le preneur, soit d'une manière implicite par le réservoir. Dans ce second cas on peut prévoir qu'un objet qui ne donne aucun signe de vie pendant 2 minutes, est automatiquement remis dans le réservoir. Ce signe de vie peut s'exprimer par le timestamp d'une dernière méthode appelée.

Un bon exemple d'object pool dans le monde réel est une bibliothèque. Les livres sont des objets du type pooled. Ils sont plaçable dans le réservoir. En ce qui concerne la bibliothèque, elle joue le rôle du pool (réservoir). Il y a également un troisième participant, le client. En occurrence, il s'agit de la personne qui va emprunter des livres. On se basera sur cette illustration pour montrer comment utiliser object pool.

Exemple object pool en Java

class Book{
private final int id;
private final String title;
private int borrowCode;

public Book(final int id, final String title){
this.id=id;
this.title=title;
Library.addToPool(this);
}

public void setBorrowCode(final int borrowCode){
this.borrowCode=borrowCode;
}

public int getId(){
return this.id;
}

public String getTitle(){
return this.title;
}

public int getBorrowCode(){
return this.borrowCode;
}

@Override
public String toString(){
return "Book {"+getId()+", "+getTitle()+"}";
}
}

Le livre est le pooled object. Composé de simples setters et getters, son constructeur invoque la méthode qui le rajoute dans la bibliothèque en tant que le livre empruntable.



class Library{
private static final Map> available=new TreeMap>();
private static final Map unavailable=new TreeMap();

public static synchronized Book getBook(final String title) throws Exception{
if(available.containsKey(title)){
List<Book> books=available.get(title);
final Book book=books.get(0);
book.setBorrowCode(new Random().nextInt());
unavailable.put(book.getBorrowCode(), book);
books.remove(0);
int index=0;
final List<Book> newList=new ArrayList<Book>();
final Iterator<Book> iterator=books.iterator();
while(iterator.hasNext()){
newList.add(index, iterator.next());
index++;
}
books.clear();
books=null;
available.put(title, newList);
return book;
}
throw new Exception("Book named "+title+" is not available");
}

public synchronized static void returnToPool(final Book book){
unavailable.remove(book.getBorrowCode());
book.setBorrowCode(0);
addToPool(book);
}

public synchronized static void addToPool(final Book book){
List books=available.get(book.getTitle());
if(books==null){
books=new ArrayList<Book>();
}
books.add(book);
available.put(book.getTitle(), books);
}

public static String getStock(){
return "Library : available books {"+available+"}, "+"unavailable books { "+unavailable+" } ";
}
}

Ce pool possède 3 méthodes :
- getBook() : c'est elle qui récupère une instance dans le réservoir et réorganise à chaque fois la collection des instances restant à disposition.
- returnToPool() : ici on remet un objet à l'état "disponible". Elle met également une nouvelle valeur pour le code d'emprunt. 0 correspond à la valeur par défaut. De cette manière on reçoit l'objet dans le même état que pendant son initialisation.
- addToPool() : la méthode qui rajout un object dans la collection des objets disponibles à l'utilisation.


public static void main(final String[] args){
final Book book1=new Book(1, "World War 1939-1945");
final Book book2=new Book(2, "World War 1939-1945");
final Book book4=new Book(3, "World War 1939-1945");
final Book book5=new Book(4, "World War 1939-1945");
final Book book6=new Book(5, "World War 1939-1945");
final Book book7=new Book(1, "World War 1914-1918");
final Book book8=new Book(2, "World War 1914-1918");
final Book book9=new Book(3, "World War 1914-1918");
System.out.println("Library state : "+Library.getStock());
// borrow some books from world war 1914-1918
try{
final Book borrow1=Library.getBook("World War 1914-1918");
}
catch(final Exception e){
e.printStackTrace();
}
System.out.println("#1 borrow : "+Library.getStock());
try{
final Book borrow2=Library.getBook("World War 1914-1918");
}
catch(final Exception e){
e.printStackTrace();
}
System.out.println("21 borrow : "+Library.getStock());
try{
final Book borrow3=Library.getBook("World War 1914-1918");
System.out.println("#3 borrow : "+Library.getStock());
// now return the last borrow
Library.returnToPool(borrow3);
}
catch(final Exception e){
e.printStackTrace();
}
System.out.println("#3 borrw returned : "+Library.getStock());
try{
final Book borrow4=Library.getBook("World War 1914-1918");
System.out.println("#4 borrow : "+Library.getStock());
}
catch(final Exception e){
e.printStackTrace();
}
}

Ce code client crée d'abord les instances de la classe Book. Comme on a vu plus haut, elles sont immédiatement rajoutées dans le réservoir. Ensuite on effectue 3 emprunts du livre consacré à la 1e Guerre Mondiale. A chaque fois on vérifie l'état du stock des livres empruntables. Le dernier emprunt se termine aussitôt qu'il a été fait. Après une vérification des livres, on effectue un quatrième emprunt. On s'aperçoit que c'est bien le 3e exemplaire rajouté dans la bibliothèque qui est pris. Il n'y a aucune création d'un nouvel objet.

Et pour le constater, voici le résultat de chaque System.out.println :

Library state : Library : available books {{World War 1914-1918=[Book {1, World War 1914-1918}, Book {2, World War 1914-1918}, Book {3, World War 1914-1918}], World War 1939-1945=[Book {1, World War 1939-1945}, Book {2, World War 1939-1945}, Book {3, World War 1939-1945}, Book {4, World War 1939-1945}, Book {5, World War 1939-1945}]}}, unavailable books { {} }
#1 borrow : Library : available books {{World War 1914-1918=[Book {2, World War 1914-1918}, Book {3, World War 1914-1918}], World War 1939-1945=[Book {1, World War 1939-1945}, Book {2, World War 1939-1945}, Book {3, World War 1939-1945}, Book {4, World War 1939-1945}, Book {5, World War 1939-1945}]}}, unavailable books { {2020321527=Book {1, World War 1914-1918}} }
21 borrow : Library : available books {{World War 1914-1918=[Book {3, World War 1914-1918}], World War 1939-1945=[Book {1, World War 1939-1945}, Book {2, World War 1939-1945}, Book {3, World War 1939-1945}, Book {4, World War 1939-1945}, Book {5, World War 1939-1945}]}}, unavailable books { {1343281123=Book {2, World War 1914-1918}, 2020321527=Book {1, World War 1914-1918}} }
#3 borrow : Library : available books {{World War 1914-1918=[], World War 1939-1945=[Book {1, World War 1939-1945}, Book {2, World War 1939-1945}, Book {3, World War 1939-1945}, Book {4, World War 1939-1945}, Book {5, World War 1939-1945}]}}, unavailable books { {864084852=Book {3, World War 1914-1918}, 1343281123=Book {2, World War 1914-1918}, 2020321527=Book {1, World War 1914-1918}} }
#3 borrw returned : Library : available books {{World War 1914-1918=[Book {3, World War 1914-1918}], World War 1939-1945=[Book {1, World War 1939-1945}, Book {2, World War 1939-1945}, Book {3, World War 1939-1945}, Book {4, World War 1939-1945}, Book {5, World War 1939-1945}]}}, unavailable books { {1343281123=Book {2, World War 1914-1918}, 2020321527=Book {1, World War 1914-1918}} }
#4 borrow : Library : available books {{World War 1914-1918=[], World War 1939-1945=[Book {1, World War 1939-1945}, Book {2, World War 1939-1945}, Book {3, World War 1939-1945}, Book {4, World War 1939-1945}, Book {5, World War 1939-1945}]}}, unavailable books { {-2041358764=Book {3, World War 1914-1918}, 1343281123=Book {2, World War 1914-1918}, 2020321527=Book {1, World War 1914-1918}} }


On a vu que l'object pool peut être utile dans des scénarios avec un réservoir d'objets gourmands à créer. Cependant, certains critiques déconseillent l'usage de pooling manuel. Dans les JVM modernes, la création et la destruction des objets de courte durée est assez rapide. La mise en place d'un pool manuel pour ce type d'objets, ne peut souvent qu'introduire des erreurs. Avant la mise en place de l'object pool il faut effectuer quelques tests de performances, en prévoyant des cas extrêmes de disponibilité des ressources. Dans l'implémentation naïve, ce patron de conception peut en effet se retourner contre nous.
Bartosz KONIECZNY 27-11-2013 06:19 design patterns
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony1

Comment afficher tous les paramètres d'une requête ?

Pour afficher tous les paramètres d'une requête, il suffit d'appeler la méthode getParameterHolder()->getAll() .