Creating Object Pool in Java
Join the DZone community and get the full member experience.
Join For FreeIn this post, we will take a look at how we can create an object pool in Java.
In recent times, JVM performance has been multiplied manifold and so object creation is no longer considered as expensive as it was done earlier. But there are few objects, for which creation of new object still seems to be slight costly as they are not considered as lightweight objects. e.g.: database connection objects, parser objects, thread creation etc. In any application we need to create multiple such objects. Since creation of such objects is costly, it’s a sure hit for the performance of any application. It would be great if we can reuse the same object again and again.
Object Pools are used for this purpose. Basically, object pools can be visualized as a storage where we can store such objects so that stored objects can be used and reused dynamically. Object pools also controls the life-cycle of pooled objects.
As we understood the requirement, let’s come to real stuff. Fortunately, there are various open source object pooling frameworks available, so we do not need to reinvent the wheel.
In this post we will be using apache commons pool to create our own object pool. At the time of writing this post Version 2.2 is the latest, so let us use this.
The basic thing we need to create is-
1. A pool to store heavyweight objects (pooled objects).
2. A simple interface, so that client can -
a.) Borrow pooled object for its use.
b.) Return the borrowed object after its use.
Let’s start with Parser Objects.
361package blog.techcypher.parser;
2
3/**
4 * Abstract definition of Parser.
5 *
6 * @author abhishek
7 *
8 */
9public interface Parser<E, T> {
10
11 /**
12 * Parse the element E and set the result back into target object T.
13 *
14 * @param elementToBeParsed
15 * @param result
16 * @throws Exception
17 */
18 public void parse(E elementToBeParsed, T result) throws Exception;
19
20
21 /**
22 * Tells whether this parser is valid or not. This will ensure the we
23 * will never be using an invalid/corrupt parser.
24 *
25 * @return
26 */
27 public boolean isValid();
28
29
30 /**
31 * Reset parser state back to the original, so that it will be as
32 * good as new parser.
33 *
34 */
35 public void reset();
36}
Let’s implement a simple XML Parser over this as below:
x1package blog.techcypher.parser.impl;
2
3import blog.techcypher.parser.Parser;
4
5/**
6 * Parser for parsing xml documents.
7 *
8 * @author abhishek
9 *
10 * @param <E>
11 * @param <T>
12 */
13public class XmlParser<E, T> implements Parser<E, T> {
14 private Exception exception;
15
16
17 public void parse(E elementToBeParsed, T result) throws Exception {
18 try {
19 System.out.println("[" + Thread.currentThread().getName()+ "]: Parser Instance:" + this);
20 // Do some real parsing stuff.
21
22 } catch(Exception e) {
23 this.exception = e;
24 e.printStackTrace(System.err);
25 throw e;
26 }
27 }
28
29
30 public boolean isValid() {
31 return this.exception == null;
32 }
33
34
35 public void reset() {
36 this.exception = null;
37 }
38
39}
At this point, as we have parser object we should create a pool to store these objects.
Here, we will be using GenericObjectPool to store the parse objects. Apache commons pool has already build-in classes for pool implementation. GenericObjectPool can be used to store any object. Each pool can contain same kind of object and they have factory associated with them.
GenericObjectPool provides a wide variety of configuration options, including the ability to cap the number of idle or active instances, to evict instances as they sit idle in the pool, etc.
If you want to create multiple pools for different kind of objects (e.g. parsers, converters, device connections etc.) then you should use GenericKeyedObjectPool .
531package blog.techcypher.parser.pool;
2
3import org.apache.commons.pool2.PooledObjectFactory;
4import org.apache.commons.pool2.impl.GenericObjectPool;
5import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
6
7import blog.techcypher.parser.Parser;
8
9/**
10 * Pool Implementation for Parser Objects.
11 * It is an implementation of ObjectPool.
12 *
13 * It can be visualized as-
14 * +-------------------------------------------------------------+
15 * | ParserPool |
16 * +-------------------------------------------------------------+
17 * | [Parser@1, Parser@2,...., Parser@N] |
18 * +-------------------------------------------------------------+
19 *
20 * @author abhishek
21 *
22 * @param <E>
23 * @param <T>
24 */
25public class ParserPool<E, T> extends GenericObjectPool<Parser<E, T>>{
26
27 /**
28 * Constructor.
29 *
30 * It uses the default configuration for pool provided by
31 * apache-commons-pool2.
32 *
33 * @param factory
34 */
35 public ParserPool(PooledObjectFactory<Parser<E, T>> factory) {
36 super(factory);
37 }
38
39 /**
40 * Constructor.
41 *
42 * This can be used to have full control over the pool using configuration
43 * object.
44 *
45 * @param factory
46 * @param config
47 */
48 public ParserPool(PooledObjectFactory<Parser<E, T>> factory,
49 GenericObjectPoolConfig config) {
50 super(factory, config);
51 }
52
53}
As we can see, the constructor of pool requires a factory to manage lifecycle of pooled objects. So we need to create a parser factory which can create parser objects.
Commons pool provide generic interface for defining a factory(PooledObjectFactory). PooledObjectFactory create and manage PooledObjects . These object wrappers maintain object pooling state, enabling PooledObjectFactory methods to have access to data such as instance creation time or time of last use.
A DefaultPooledObject is provided, with natural implementations for pooling state methods. The simplest way to implement a PoolableObjectFactory is to have it extend BasePooledObjectFactory . This factory provides a makeObject() that returns wrap(create()) where create and wrap are abstract. We provide an implementation of create to create the underlying objects that we want to manage in the pool and wrap to wrap created instances in PooledObjects.
So, here is our factory implementation for parser objects-
401package blog.techcypher.parser.pool;
2
3import org.apache.commons.pool2.BasePooledObjectFactory;
4import org.apache.commons.pool2.PooledObject;
5import org.apache.commons.pool2.impl.DefaultPooledObject;
6
7import blog.techcypher.parser.Parser;
8import blog.techcypher.parser.impl.XmlParser;
9
10/**
11 * Factory to create parser object(s).
12 *
13 * @author abhishek
14 *
15 * @param <E>
16 * @param <T>
17 */
18public class ParserFactory<E, T> extends BasePooledObjectFactory<Parser<E, T>> {
19
20
21 public Parser<E, T> create() throws Exception {
22 return new XmlParser<E, T>();
23 }
24
25
26 public PooledObject<Parser<E, T>> wrap(Parser<E, T> parser) {
27 return new DefaultPooledObject<Parser<E,T>>(parser);
28 }
29
30
31 public void passivateObject(PooledObject<Parser<E, T>> parser) throws Exception {
32 parser.getObject().reset();
33 }
34
35
36 public boolean validateObject(PooledObject<Parser<E, T>> parser) {
37 return parser.getObject().isValid();
38 }
39
40}
Now, at this point we have successfully created our pool to store parser objects and we have a factory as well to manage the life-cycle of parser objects.
You should notice that, we have implemented couple of extra methods-
1. boolean validateObject(PooledObject obj): This is used to validate an object borrowed from the pool or returned to the pool based on configuration. By default, validation remains off. Implementing this ensures that client will always get a valid object from the pool.
Since, we have everything in place, let’s create a test to test this pool. Pool clients can –
1. Get object by calling pool.borrowObject()
2. Return the object back to pool by calling pool.returnObject(object)
Below is our code to test Parser Pool-
951package blog.techcypher.parser;
2import static org.junit.Assert.fail;
3
4import java.util.concurrent.ArrayBlockingQueue;
5import java.util.concurrent.ExecutorService;
6import java.util.concurrent.ThreadPoolExecutor;
7import java.util.concurrent.TimeUnit;
8import java.util.concurrent.atomic.AtomicInteger;
9
10import junit.framework.Assert;
11
12import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
13import org.junit.Before;
14import org.junit.Test;
15
16import blog.techcypher.parser.pool.ParserFactory;
17import blog.techcypher.parser.pool.ParserPool;
18/**
19 * Test case to test-
20 * 1. object creation by factory
21 * 2. object borrow from pool.
22 * 3. returning object back to pool.
23 *
24 * @author abhishek
25 *
26 */
27public class ParserFactoryTest {
28
29 private ParserPool<String, String> pool;
30 private AtomicInteger count = new AtomicInteger(0);
31
32
33 public void setUp() throws Exception {
34 GenericObjectPoolConfig config = new GenericObjectPoolConfig();
35 config.setMaxIdle(1);
36 config.setMaxTotal(1);
37
38 /*---------------------------------------------------------------------+
39 |TestOnBorrow=true --> To ensure that we get a valid object from pool |
40 |TestOnReturn=true --> To ensure that valid object is returned to pool |
41 +---------------------------------------------------------------------*/
42 config.setTestOnBorrow(true);
43 config.setTestOnReturn(true);
44 pool = new ParserPool<String, String>(new ParserFactory<String, String>(), config);
45 }
46
47
48 public void test() {
49 try {
50 int limit = 10;
51
52 ExecutorService es = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(limit));
53
54 for (int i=0; i<limit; i++) {
55 Runnable r = new Runnable() {
56
57 public void run() {
58 Parser<String, String> parser = null;
59 try {
60 parser = pool.borrowObject();
61 count.getAndIncrement();
62 parser.parse(null, null);
63
64 } catch (Exception e) {
65 e.printStackTrace(System.err);
66
67 } finally {
68 if (parser != null) {
69 pool.returnObject(parser);
70 }
71 }
72 }
73 };
74
75 es.submit(r);
76 }
77
78 es.shutdown();
79
80 try {
81 es.awaitTermination(1, TimeUnit.MINUTES);
82
83 } catch (InterruptedException ignored) {}
84
85 System.out.println("Pool Stats:\n Created:[" + pool.getCreatedCount() + "], Borrowed:[" + pool.getBorrowedCount() + "]");
86 Assert.assertEquals(limit, count.get());
87 Assert.assertEquals(count.get(), pool.getBorrowedCount());
88 Assert.assertEquals(1, pool.getCreatedCount());
89
90 } catch (Exception ex) {
91 fail("Exception:" + ex);
92 }
93 }
94
95}
Result:
121[pool-1-thread-1]: Parser Instance:blog.techcypher.parser.impl.XmlParser
2[pool-1-thread-2]: Parser Instance:blog.techcypher.parser.impl.XmlParser
3[pool-1-thread-3]: Parser Instance:blog.techcypher.parser.impl.XmlParser
4[pool-1-thread-4]: Parser Instance:blog.techcypher.parser.impl.XmlParser
5[pool-1-thread-5]: Parser Instance:blog.techcypher.parser.impl.XmlParser
6[pool-1-thread-8]: Parser Instance:blog.techcypher.parser.impl.XmlParser
7[pool-1-thread-7]: Parser Instance:blog.techcypher.parser.impl.XmlParser
8[pool-1-thread-9]: Parser Instance:blog.techcypher.parser.impl.XmlParser
9[pool-1-thread-6]: Parser Instance:blog.techcypher.parser.impl.XmlParser
10[pool-1-thread-10]: Parser Instance:blog.techcypher.parser.impl.XmlParser
11Pool Stats:
12 Created:[1], Borrowed:[10]
You can easily see that single parser object was created and reused dynamically.
Commons Pool 2 stands far better in term of performance and scalability over Commons Pool 1. Also, version 2 includes robust instance tracking and pool monitoring. Commons Pool 2 requires JDK 1.6 or above. There are lots of configuration options to control and manage the life-cycle of pooled objects.
And so ends our long post… :-)
Hope this article helped. Keep learning!
Opinions expressed by DZone contributors are their own.
Comments