simple-pool
A simple object pool implementation. It is designed to be small and lightweight.
This library does not enforce an exception type like IOException
, nor is it limited to only unchecked exceptions. Instead, each pool and its related classes have a generic type for the exception type that can be thrown. This allows callers to provide their own exception types. For cases where no exceptions are thrown, a marker exception type is provided: None.
Creating pools
A pool needs 3 to 4 things:
- A type of object that can be pooled. This should be provided as a sub class of PoolableObject.
- Configuration for the pool. This includes the initial and maximum pool sizes.
- A factory for creating pooled objects.
- Optionally, a logger that will be triggered for specific events in the pool.
Class Pool provides two factory methods that accept a Supplier
instead of the factory, and use None
as the exception type. For instance, where MyObject
extends PoolableObject<None>
:
Pool<MyObject, None> pool = Pool.throwingNone(config, MyObject::new, logger);
PoolableObject
PoolableObject
is the base class for all object types that can be stored in a pool. Sub classes should implement isValid
, doReleaseResources
and optionally doReleaseResourcesQuietly
. In addition, they should provide a method that calls release
. This method should be called to return the object to the pool. The close
method of interface AutoCloseable
Closeable
is a good candidate.
A PoolableObject
can have other references added. Instances are only returned to the pool if all references have been removed. This can be used if the object can expose other objects like InputStream
or OutputStream
. By adding such objects as references to the object and removing the references when these objects are no longer necessary (e.g. when they are closed), it's possible to delay returning objects to the pool. This allows sub classes to be released without having to worry about these references themselves. For instance:
class MyConnection extends PoolableObject<IOException> {
...
InputStream getInputStream() throws IOException {
InputStream inputStream = ...;
// when inputStream.close() is called, it should call removeReference(inputStream)
addReference(customInputStream);
return inputStream;
}
}
Configuration
Class PoolConfig can be used to provide configuration. It currently supports 4 settings:
- The initial size (default: 1) - the initial number of objects in the pool.
- The maximum pool size (default: 5).
- The maximum wait time (default: indefinitely) - if an object is acquired without an explicit wait time, this value will be used.
- The maximum idle time (default: indefinitely) - any objects that have been in the pool longer than this time will be removed from the pool.
When acquiring objects from the pool, an idle object will be used if available. Otherwise, if the current pool size is smaller than the maximum, a new object will be created.
Factory
Interface PoolableObjectFactory is a functional interface similar to Supplier
that is used to create new objects. It shares the exception type it can throw with the PoolableObject
instances it can create.
Logger
Class PoolLogger can be used to provide logging. It uses SLF4J for this; if it is not available on the class path logging will be disabled.
A pool logger can use a custom logger name, log message prefix or object prefix. This allows you to customize the logging somewhat. For instance, with log message prefix ftp.example.org -
and object prefix connection-
, a message could look like this:
ftp.example.org - Acquired object 'connection-4' from pool; idle: 2, pool size: 5
Using pools
A pool only has a few methods:
acquire
will return an object from the pool, blocking until one is available. The method is overloaded to provide the wait timeout, as well as the exception that is thrown when the timeout expires.acquireNow
will return an object from the pool, wrapped in anOptional
. If no object is available immediately, an emptyOptional
is returned instead.acquireOrCreate
will return an object from the pool. If no object is available immediately, a new non-pooled object is created instead. This can be used in case an object is needed (soacquireNow
cannot be used), but blocking may cause deadlock or similar issues. In these cases, it's usually better to just use a disposable object.forAllIdleObjects
will internally acquire all idle objects, perform an operation on them, and then return them to the pool. This can for instance be used to send keep-alive signals to all idle objects.shutdown
will release the resources of all idle objects, remove them, and prevent any other operation on the pool. Any call toacquire
that is blocked will throw anIllegalStateException
. This can be used to cleanup all resources.isActive
can be used to query the state of the pool. It will returntrue
untilshutdown
is called.
An acquired object should eventually be released. It is up to the PoolableObject
sub class to expose the release
method, either by overriding it and making it public, or calling it from a different public method like close
.
For instance, assuming MyObject
extends PoolableObject<IOException>
, implements AutoCloseable
, and calls release
from its close
method:
Pool<MyObject, IOException> pool = new Pool<>(config, MyObject::new, logger);
try (MyObject object = pool.acquire()) { // note: may throw InterruptedException
// use object as needed
}
// object.close() will have called object.release(), which means object has been returned to the pool
ObjectWrapper
To make it easy to create pools of objects that are always valid and don't need any resource cleanup, class ObjectWrapper is a PoolableObject
sub class that simply wraps a value. It implements AutoCloseable
, so it can be used in try-with-resources blocks:
Pool<ObjectWrapper<MyObject>, None> pool = ObjectWrapper.newPool(config, MyObject::new, logger);
try (ObjectWrapper<MyObject> wrapper = pool.acquire()) { // note: may throw InterruptedException
MyObject object = wrapper.value();
// use object as needed
}
// wrapper.close() will have returned the wrapper to the pool