Java Concurrent Dynamic Object Pool for non Thread Safe Objects using Blocking Queue

I tried to integrate an external non-thread-safe library to my web project; I found out that it’s too expensive to create an instance of the object for each client thread. Initially, I solved this issue by just sharing the same object across all the concurrent client threads, and synchronized this object externally.

However, the performance is still not acceptable; as a result, I designed a resource pool to hold fixed amount of the objects, and recycle them when the threads don’t need them anymore.

I want that the resource objects in the pool are dynamically created instead of creating all of them in the constructor. The pool will be initially empty, and when a client thread acquires a resource object, the pool can create a new resource on demand. Once the numbers of created objects are reached the size of the pool; then the new client threads will be blocked, and wait for other thread to recycle the resource.

Finally, the pool should be fair, and the fairness ensures that the first thread that asks is the first thread that gets; otherwise there is probability that some threads will just wait forever.

This concurrent object pool can be built by the blocking queue in Java concurrent package, and one of the implementation, ArrayBlockingQueue supports fairness which we require.

In this implementation, I use ReentrantLock to control if we can create a new object in the pool. As a result, in the non-dynamic creation mode i.e., creating all the objects in the constructor, this lock will always be locked. In the dynamic creation mode, in each time, only one object can be created, so if there is another thread acquiring this object, it will get the object from pool.take() which is blocking remove, and waits for a new available resource in the queue.

The source code of this ResourcePool can be downloaded at ResourcePool.java

public abstract class ResourcePool {
    private final BlockingQueue pool;
    private final ReentrantLock lock = new ReentrantLock();
    private int createdObjects = 0;
    private int size;

    protected ResourcePool(int size) {
        this(size, false);
    }

    protected ResourcePool(int size, Boolean dynamicCreation) {
        // Enable the fairness; otherwise, some threads
        // may wait forever.
        pool = new ArrayBlockingQueue<>(size, true);
        this.size = size;
        if (!dynamicCreation) {
            lock.lock();
        }
    }

    public Resource acquire() throws Exception {
        if (!lock.isLocked()) {
            if (lock.tryLock()) {
                try {
                    ++createdObjects;
                    return createObject();
                } finally {
                    if (createdObjects < size) lock.unlock();
                }
            }
        }
        return pool.take();
    }

    public void recycle(Resource resource) throws Exception {
        // Will throws Exception when the queue is full,
        // but it should never happen.
        pool.add(resource);
    }

    public void createPool() {
        if (lock.isLocked()) {
            for (int i = 0; i < size; ++i) {
                pool.add(createObject());
                createdObjects++;
            }
        }
    }

    protected abstract Resource createObject();
}

Note that in the acquire() method, I check lock.isLocked() first and then do lock.tryLock(). The ordering does matter, and we can not remove isLocked() due to that tryLock() seems to do the same work. The reason is that we will not unlock the ReentrantLock when createdObjects > size, which means if a thread get the lock and create a new resource of object, the thread still hold the lock such that it will still enter the synchronized region since it’s re-entrance lock. We use isLocked() to avoid this situation.

The DateFormat class is not thread-safe in Java. The Java document states that “Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.” I will use SimpleDateFormat as example to demonstrate how to use the ResourcePool class.

In the following example, there are 5 client threads simultaneously acquiring two DataTimeFormat objects in resource pool, and those client threads will do 30 computations in total. This code can be downloaded at ResourcePoolExample.java.

class DataTimeFormatResourcePool extends ResourcePool<SimpleDateFormat> {

    DataTimeFormatResourcePool(int size, Boolean dynamicCreation) {
        super(size, dynamicCreation);
        createPool();
    }

    @Override
    protected SimpleDateFormat createObject() {
        return new SimpleDateFormat("yyyyMMdd");
    }

    public Date convert(String input) throws Exception {
        SimpleDateFormat format = acquire();
        try {
            return format.parse(input);
        } finally {
            recycle(format);
        }
    }
}

public class ResourcePoolExample {
    public static void main(String args[]) {
        final DataTimeFormatResourcePool pool = new DataTimeFormatResourcePool(2, true);

        Callable<Date> task = new Callable<Date>() {
            @Override
            public Date call() throws Exception {
                return pool.convert("20130224");
            }
        };

        ExecutorService exec = Executors.newFixedThreadPool(5);
        List<Future<Date>> results = new ArrayList<>();

        for (int i = 0; i < 30; i++) {
            results.add(exec.submit(task));
        }
        exec.shutdown();
        try {
            for (Future<Date> result : results) {
                System.out.println(result.get());
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Dialogue & Discussion