Dans cet article, nous discutons principalement de la manière d'implémenter un pool d'objetsen Java. Ces dernières années, les performances de la machine virtuelle Java ont été considérablement améliorées à tous égards, de sorte que pour la plupart des objets, il n'est pas nécessaire d'utiliser des pools d'objets pour améliorer les performances. La raison fondamentale est que créer un nouvel objet n’est plus aussi coûteux qu’avant.
Cependant, il existe encore certains objets dont la surcharge de création est très élevée, tels que les threads, les connexions à des bases de données et d'autres objets non légers. Dans toute application, nous utiliserons certainement plusieurs de ces objets. S'il existe un moyen pratique de créer et de gérer un pool de ces objets, afin que ces objets puissent être réutilisés dynamiquement, et que le code client n'ait pas besoin de se soucier de leur cycle de vie, ce sera toujours très puissant.
Avant de commencer à écrire du code, voyons quelles fonctions le prochain pool d'objets doit remplir.
Le pool d'objets doit pouvoir revenir au client s'il y a des objets disponibles.
Une fois que le client a remis les objets dans la piscine, il peut réutiliser ces objets.
Le pool d'objets permet de créer de nouveaux objets pour répondre aux besoins croissants du client.
Il doit y avoir un mécanisme pour fermer correctement le pool afin de garantir qu'aucune fuite de mémoire ne se produise après la fermeture.
Inutile de dire que les points ci-dessus sont les fonctions de base de l'interface du pool de connexions que nous souhaitons exposer au client.
Notre interface déclarée est la suivante :
package com.test.pool; /** * Represents a cached pool of objects. * * @author Swaranga * * @param <T> the type of object to pool. */ public interface Pool<T> { /** * Returns an instance from the pool. * The call may be a blocking one or a non-blocking one * and that is determined by the internal implementation. * * If the call is a blocking call, * the call returns immediately with a valid object * if available, else the thread is made to wait * until an object becomes available. * In case of a blocking call, * it is advised that clients react * to {@link InterruptedException} which might be thrown * when the thread waits for an object to become available. * * If the call is a non-blocking one, * the call returns immediately irrespective of * whether an object is available or not. * If any object is available the call returns it * else the call returns < code >null< /code >. * * The validity of the objects are determined using the * {@link Validator} interface, such that * an object < code >o< /code > is valid if * < code > Validator.isValid(o) == true < /code >. * * @return T one of the pooled objects. */ T get(); /** * Releases the object and puts it back to the pool. * * The mechanism of putting the object back to the pool is * generally asynchronous, * however future implementations might differ. * * @param t the object to return to the pool */ void release(T t); /** * Shuts down the pool. In essence this call will not * accept any more requests * and will release all resources. * Releasing resources are done * via the < code >invalidate()< /code > * method of the {@link Validator} interface. */ void shutdown(); }
Afin de prendre en charge n'importe quel objet, l'interface ci-dessus est délibérément conçue pour être simple et universelle. Il fournit des méthodes pour obtenir/retourner des objets du pool, ainsi qu'un mécanisme pour fermer le pool afin que les objets puissent être libérés.
Implémentons maintenant cette interface. Avant de commencer, il convient de mentionner qu'une méthode de publication idéale devrait d'abord essayer de vérifier si l'objet renvoyé par le client peut être réutilisé. Si c’est le cas, jetez-le dans la piscine. Sinon, jetez l’objet. Nous espérons que toutes les implémentations de cette interface Pool pourront suivre cette règle. Avant de démarrer la classe d'implémentation spécifique, nous créons d'abord une classe abstraite pour restreindre les implémentations ultérieures à suivre ce point. La classe abstraite que nous avons implémentée s'appelle AbstractPool, et sa définition est la suivante :
package com.test.pool; /** * Represents an abstract pool, that defines the procedure * of returning an object to the pool. * * @author Swaranga * * @param <T> the type of pooled objects. */ abstract class AbstractPool <T> implements Pool <T> { /** * Returns the object to the pool. * The method first validates the object if it is * re-usable and then puts returns it to the pool. * * If the object validation fails, * some implementations * will try to create a new one * and put it into the pool; however * this behaviour is subject to change * from implementation to implementation * */ @Override public final void release(T t) { if(isValid(t)) { returnToPool(t); } else { handleInvalidReturn(t); } } protected abstract void handleInvalidReturn(T t); protected abstract void returnToPool(T t); protected abstract boolean isValid(T t); }
Dans la classe ci-dessus, nous faisons en sorte que le pool d'objets vérifie l'objet avant de le remettre dans le pool. Les implémentations spécifiques sont libres de choisir comment implémenter ces trois méthodes afin de personnaliser leur propre comportement. Ils décident, en fonction de leur propre logique, comment déterminer si un objet est valide, que faire s'il n'est pas valide (méthode handleInvalidReturn) et comment remettre un objet valide dans le pool (méthode returnToPool).
Avec les classes ci-dessus, nous pouvons démarrer l'implémentation spécifique. Cependant, il existe toujours un problème : étant donné que les classes ci-dessus sont conçues pour prendre en charge des pools d'objets universels, l'implémentation spécifique ne sait pas comment vérifier la validité des objets (car les objets sont tous génériques). Nous avons donc besoin de quelque chose d'autre pour nous aider à y parvenir.
Nous avons besoin d'une méthode générale pour terminer la vérification des objets, et l'implémentation spécifique n'a pas besoin de se soucier du type d'objet. Par conséquent, nous avons introduit une nouvelle interface, Validator, qui définit des méthodes de validation des objets. La définition de cette interface est la suivante :
package com.test.pool; /** * Represents the functionality to * validate an object of the pool * and to subsequently perform cleanup activities. * * @author Swaranga * * @param < T > the type of objects to validate and cleanup. */ public static interface Validator < T > { /** * Checks whether the object is valid. * * @param t the object to check. * * @return <code>true</code> * if the object is valid else <code>false</code>. */ public boolean isValid(T t); /** * Performs any cleanup activities * before discarding the object. * For example before discarding * database connection objects, * the pool will want to close the connections. * This is done via the * <code>invalidate()</code> method. * * @param t the object to cleanup */ public void invalidate(T t); }
L'interface ci-dessus définit une méthode pour vérifier l'objet et une méthode pour invalider l'objet. Lorsque vous vous préparez à supprimer un objet et à nettoyer la mémoire, la méthode invalidate s'avère pratique. Il convient de noter que cette interface elle-même n'a aucune signification. Elle n'a de sens que lorsqu'elle est utilisée dans un pool d'objets, nous définissons donc cette interface dans l'interface Pool. C'est la même chose que Map et Map.Entry dans la bibliothèque de collection Java. Notre interface Pool devient donc comme ceci :
package com.test.pool; /** * Represents a cached pool of objects. * * @author Swaranga * * @param < T > the type of object to pool. */ public interface Pool< T > { /** * Returns an instance from the pool. * The call may be a blocking one or a non-blocking one * and that is determined by the internal implementation. * * If the call is a blocking call, * the call returns immediately with a valid object * if available, else the thread is made to wait * until an object becomes available. * In case of a blocking call, * it is advised that clients react * to {@link InterruptedException} which might be thrown * when the thread waits for an object to become available. * * If the call is a non-blocking one, * the call returns immediately irrespective of * whether an object is available or not. * If any object is available the call returns it * else the call returns < code >null< /code >. * * The validity of the objects are determined using the * {@link Validator} interface, such that * an object < code >o< /code > is valid if * < code > Validator.isValid(o) == true < /code >. * * @return T one of the pooled objects. */ T get(); /** * Releases the object and puts it back to the pool. * * The mechanism of putting the object back to the pool is * generally asynchronous, * however future implementations might differ. * * @param t the object to return to the pool */ void release(T t); /** * Shuts down the pool. In essence this call will not * accept any more requests * and will release all resources. * Releasing resources are done * via the < code >invalidate()< /code > * method of the {@link Validator} interface. */ void shutdown(); /** * Represents the functionality to * validate an object of the pool * and to subsequently perform cleanup activities. * * @author Swaranga * * @param < T > the type of objects to validate and cleanup. */ public static interface Validator < T > { /** * Checks whether the object is valid. * * @param t the object to check. * * @return <code>true</code> * if the object is valid else <code>false</code>. */ public boolean isValid(T t); /** * Performs any cleanup activities * before discarding the object. * For example before discarding * database connection objects, * the pool will want to close the connections. * This is done via the * <code>invalidate()</code> method. * * @param t the object to cleanup */ public void invalidate(T t); } }
Les préparatifs sont presque terminés. Avant de enfin commencer, nous avons besoin d'une arme ultime. C'est la fonctionnalité phare de ce pool d'objets. C'est « la capacité de créer de nouveaux objets ». Notre pool d'objets est générique, ils doivent donc savoir comment générer de nouveaux objets pour remplir le pool. Cette fonctionnalité ne peut pas s'appuyer sur le pool d'objets lui-même, il doit exister un moyen commun de créer de nouveaux objets. Cela peut être accompli via une interface ObjectFactory, qui n'a qu'une seule méthode « comment créer un nouvel objet ». Notre interface ObjectFactory est la suivante :
package com.test.pool; /** * Represents the mechanism to create * new objects to be used in an object pool. * * @author Swaranga * * @param < T > the type of object to create. */ public interface ObjectFactory < T > { /** * Returns a new instance of an object of type T. * * @return T an new instance of the object of type T */ public abstract T createNew(); }
我们的工具类都已经搞定了,现在可以开始真正实现我们的Pool接口了。因为我们希望这个池能在并发程序里面使用,所以我们会创建一个阻塞的对象池,当没有对象可用的时候,让客户端先阻塞住。我们的阻塞机制是让客户端一直阻塞直到有对象可用为止。这样的话导致我们还需要再增加一个只阻塞一定时间的方法,如果在超时时间到来前有对象可用则返回,如果超时了就返回null而不是一直等待下去。这样的实现有点类似Java并发库里的LinkedBlockingQueue,因此真正实现前我们再暴露一个接口,BlockingPool,类似于Java并发库里的BlockingQueue接口。
这里是BlockingQueue的声明:
package com.test.pool; import java.util.concurrent.TimeUnit; /** * Represents a pool of objects that makes the * requesting threads wait if no object is available. * * @author Swaranga * * @param < T > the type of objects to pool. */ public interface BlockingPool < T > extends Pool < T > { /** * Returns an instance of type T from the pool. * * The call is a blocking call, * and client threads are made to wait * indefinitely until an object is available. * The call implements a fairness algorithm * that ensures that a FCFS service is implemented. * * Clients are advised to react to InterruptedException. * If the thread is interrupted while waiting * for an object to become available, * the current implementations * sets the interrupted state of the thread * to <code>true</code> and returns null. * However this is subject to change * from implementation to implementation. * * @return T an instance of the Object * of type T from the pool. */ T get(); /** * Returns an instance of type T from the pool, * waiting up to the * specified wait time if necessary * for an object to become available.. * * The call is a blocking call, * and client threads are made to wait * for time until an object is available * or until the timeout occurs. * The call implements a fairness algorithm * that ensures that a FCFS service is implemented. * * Clients are advised to react to InterruptedException. * If the thread is interrupted while waiting * for an object to become available, * the current implementations * set the interrupted state of the thread * to <code>true</code> and returns null. * However this is subject to change * from implementation to implementation. * * * @param time amount of time to wait before giving up, * in units of <tt>unit</tt> * @param unit a <tt>TimeUnit</tt> determining * how to interpret the * <tt>timeout</tt> parameter * * @return T an instance of the Object * of type T from the pool. * * @throws InterruptedException * if interrupted while waiting */ T get(long time, TimeUnit unit) throws InterruptedException; }
BoundedBlockingPool的实现如下:
package com.test.pool; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; public final class BoundedBlockingPool extends <AbstractPool> implements <BlockingPool> { private int size; private BlockingQueue objects; private Validator validator; private ObjectFactory objectFactory; private ExecutorService executor = Executors.newCachedThreadPool(); private volatile boolean shutdownCalled; public BoundedBlockingPool( int size, Validator validator, ObjectFactory objectFactory) { super(); this.objectFactory = objectFactory; this.size = size; this.validator = validator; objects = new LinkedBlockingQueue (size); initializeObjects(); shutdownCalled = false; } public T get(long timeOut, TimeUnit unit) { if(!shutdownCalled) { T t = null; try { t = objects.poll(timeOut, unit); return t; } catch(InterruptedException ie) { Thread.currentThread().interrupt(); } return t; } throw new IllegalStateException( 'Object pool is already shutdown'); } public T get() { if(!shutdownCalled) { T t = null; try { t = objects.take(); } catch(InterruptedException ie) { Thread.currentThread().interrupt(); } return t; } throw new IllegalStateException( 'Object pool is already shutdown'); } public void shutdown() { shutdownCalled = true; executor.shutdownNow(); clearResources(); } private void clearResources() { for(T t : objects) { validator.invalidate(t); } } @Override protected void returnToPool(T t) { if(validator.isValid(t)) { executor.submit(new ObjectReturner(objects, t)); } } @Override protected void handleInvalidReturn(T t) { } @Override protected boolean isValid(T t) { return validator.isValid(t); } private void initializeObjects() { for(int i = 0; i < size; i++) { objects.add(objectFactory.createNew()); } } private class ObjectReturner implements <Callable> { private BlockingQueue queue; private E e; public ObjectReturner(BlockingQueue queue, E e) { this.queue = queue; this.e = e; } public Void call() { while(true) { try { queue.put(e); break; } catch(InterruptedException ie) { Thread.currentThread().interrupt(); } } return null; } } }
上面是一个非常基本的对象池,它内部是基于一个LinkedBlockingQueue来实现的。这里唯一比较有意思的方法就是returnToPool。因为内部的存储是一个LinkedBlockingQueue实现的,如果我们直接把返回的对象扔进去的话,如果队列已满可能会阻塞住客户端。不过我们不希望客户端因为把对象放回池里这么个普通的方法就阻塞住了。所以我们把最终将对象插入到队列里的任务作为一个异步的的任务提交给一个Executor来执行,以便让客户端线程能立即返回。
现在我们将在自己的代码中使用上面这个对象池,用它来缓存数据库连接。我们需要一个校验器来验证数据库连接是否有效。
下面是这个JDBCConnectionValidator:
package com.test; import java.sql.Connection; import java.sql.SQLException; import com.test.pool.Pool.Validator; public final class JDBCConnectionValidator implements Validator < Connection > { public boolean isValid(Connection con) { if(con == null) { return false; } try { return !con.isClosed(); } catch(SQLException se) { return false; } } public void invalidate(Connection con) { try { con.close(); } catch(SQLException se) { } } }
还有一个JDBCObjectFactory,它将用来生成新的数据库连接对象:
package com.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import com.test.pool.ObjectFactory; public class JDBCConnectionFactory implements ObjectFactory < Connection > { private String connectionURL; private String userName; private String password; public JDBCConnectionFactory( String driver, String connectionURL, String userName, String password) { super(); try { Class.forName(driver); } catch(ClassNotFoundException ce) { throw new IllegalArgumentException('Unable to find driver in classpath', ce); } this.connectionURL = connectionURL; this.userName = userName; this.password = password; } public Connection createNew() { try { return DriverManager.getConnection( connectionURL, userName, password); } catch(SQLException se) { throw new IllegalArgumentException('Unable to create new connection', se); } } }
现在我们用上述的Validator和ObjectFactory来创建一个JDBC的连接池:
package com.test; import java.sql.Connection; import com.test.pool.Pool; import com.test.pool.PoolFactory; public class Main { public static void main(String[] args) { Pool < Connection > pool = new BoundedBlockingPool < Connection > ( 10, new JDBCConnectionValidator(), new JDBCConnectionFactory('', '', '', '') ); //do whatever you like } }
为了犒劳下能读完整篇文章的读者,我这再提供另一个非阻塞的对象池的实现,这个实现和前面的唯一不同就是即使对象不可用,它也不会让客户端阻塞,而是直接返回null。具体的实现在这:
package com.test.pool; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Semaphore; public class BoundedPool < T > extends AbstractPool < T > { private int size; private Queue < T > objects; private Validator < T > validator; private ObjectFactory < T > objectFactory; private Semaphore permits; private volatile boolean shutdownCalled; public BoundedPool( int size, Validator < T > validator, ObjectFactory < T > objectFactory) { super(); this.objectFactory = objectFactory; this.size = size; this.validator = validator; objects = new LinkedList < T >(); initializeObjects(); shutdownCalled = false; } @Override public T get() { T t = null; if(!shutdownCalled) { if(permits.tryAcquire()) { t = objects.poll(); } } else { throw new IllegalStateException('Object pool already shutdown'); } return t; } @Override public void shutdown() { shutdownCalled = true; clearResources(); } private void clearResources() { for(T t : objects) { validator.invalidate(t); } } @Override protected void returnToPool(T t) { boolean added = objects.add(t); if(added) { permits.release(); } } @Override protected void handleInvalidReturn(T t) { } @Override protected boolean isValid(T t) { return validator.isValid(t); } private void initializeObjects() { for(int i = 0; i < size; i++) { objects.add(objectFactory.createNew()); } } }
考虑到我们现在已经有两种实现,非常威武了,得让用户通过工厂用具体的名称来创建不同的对象池了。工厂来了:
package com.test.pool; import com.test.pool.Pool.Validator; /** * Factory and utility methods for * {@link Pool} and {@link BlockingPool} classes * defined in this package. * This class supports the following kinds of methods: * * <ul> * <li> Method that creates and returns a default non-blocking * implementation of the {@link Pool} interface. * </li> * * <li> Method that creates and returns a * default implementation of * the {@link BlockingPool} interface. * </li> * </ul> * * @author Swaranga */ public final class PoolFactory { private PoolFactory() { } /** * Creates a and returns a new object pool, * that is an implementation of the {@link BlockingPool}, * whose size is limited by * the <tt> size </tt> parameter. * * @param size the number of objects in the pool. * @param factory the factory to create new objects. * @param validator the validator to * validate the re-usability of returned objects. * * @return a blocking object pool * bounded by <tt> size </tt> */ public static < T > Pool < T > newBoundedBlockingPool( int size, ObjectFactory < T > factory, Validator < T > validator) { return new BoundedBlockingPool < T > ( size, validator, factory); } /* * Creates a and returns a new object pool, * that is an implementation of the {@link Pool} * whose size is limited * by the <tt> size </tt> parameter. * * @param size the number of objects in the pool. * @param factory the factory to create new objects. * @param validator the validator to validate * the re-usability of returned objects. * * @return an object pool bounded by <tt> size </tt> */ public static < T > Pool < T > newBoundedNonBlockingPool( int size, ObjectFactory < T > factory, Validator < T > validator) { return new BoundedPool < T >(size, validator, factory); } }
现在我们的客户端就能用一种可读性更强的方式来创建对象池了:
package com.test; import java.sql.Connection; import com.test.pool.Pool; import com.test.pool.PoolFactory; public class Main { public static void main(String[] args) { Pool < Connection > pool = PoolFactory.newBoundedBlockingPool( 10, new JDBCConnectionFactory('', '', '', ''), new JDBCConnectionValidator()); //do whatever you like } }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!