Link Search Menu Expand Document

The Object Pool

Object Pools are also known as Resource Pools. They are designed to reduce/ optimize the resource cost (time/ memory/ cpu etc) by reusing the instances of stateless objects, that are otherwise expensive to create.


What problems does it solve?

Reduces the instantiation cost and does a performance boost. This is achieved by reusing the instances of stateless objects, that will be kept in a pool in the memory.

You should consider using an Object Pool when:

  • Creating a new instance is an expensive operation
  • New instances needs to be created often
  • The number of the instances, that are used at the same time is low (even limited)
  • You want to limit the number of instances

Glossary:

  • Reusable - the resource we are going to reuse instead of creating new instance every time
  • Client - will use an instance of Reusable
  • ReusablePool - manages the reusable objects

Pros:

  • To boost the performance
  • To reuse a costly resource (fewer objects are created)
  • To control (limit) GC by limiting the number of instances and keeping them “in use”

Cons:

  • You should take care of the pool and the number of the allocated objects
  • All allocated objects will be kept in the memory (even if they are not needed)
  • It can be a potential bottleneck of your system
  • Violates GC best practices, because objects should not be referenced, if they are no longer in use

Don’t use the Object Pool Design Pattern, if you are not 100% sure, that there is no other way to achieve your goal!

How to recognize it?

  • When you release a object and then if you call the creational method again, you receive the “released” instance

  • When you call a creational method and you always receive one of few instances.

How can be improved?

  • You should use a Singleton for the Pool.
  • You can implement a “growable” object pool and/ or a pool with a limited number of instances
  • You must ensure that the number of objects won’t “grow” forever

Scenarios

  • When you have to control the number of instances

  • When you have to create often network connections

  • When you have to create often database/ datasource connections

  • When you have to connect often to some external resource

  • When you have to create often new threads

  • In games, to avoid (to try to control) GC in order to improve user experience (by keeping the number of instances low and “in use”).

Example 1

We are going to implement a thread-safe connection pool with limited number of connections in use (5 max).

Source Code

1). Create a generic connection interfaces. This is the reusable resource.

public interface Connection {

    void release();
}

2). Create an example implementation and simulate long creation time (2 seconds in our case)

import lombok.ToString;
import java.util.UUID;

@ToString
public class ConnectionImpl implements Connection {

    private String id;

    ConnectionImpl() {
        this.id = UUID.randomUUID().toString();
        initialize();
    }

    private void initialize() {
        long start = System.currentTimeMillis();

        try {
            Thread.sleep(2000L); // we will simulate that instantiation takes 2 seconds
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("New connection with id=" + this.id + " created. It took " + (System.currentTimeMillis() - start) + "ms.");
    }

    public void release() {
        ConnectionPool.getPool().releaseConnection(this);
    }
}

3). Create the connection pool

import lombok.AccessLevel;
import lombok.Getter;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

public class ConnectionPool {

    private static final int MAX_PARALLEL_CONNECTIONS = 5;

    private static volatile ConnectionPool instance;

    @Getter(AccessLevel.PRIVATE)
    private BlockingQueue<Connection> connectionsInUse = new LinkedBlockingDeque(MAX_PARALLEL_CONNECTIONS);

    @Getter(AccessLevel.PRIVATE)
    private BlockingQueue<Connection> freeConnections = new LinkedBlockingDeque(MAX_PARALLEL_CONNECTIONS);


    public static ConnectionPool getPool() {
        if (instance == null) {
            synchronized (ConnectionPool.class) {
                if (instance == null) {
                    instance = new ConnectionPool();
                }
            }
        }
        return instance;
    }

    private ConnectionPool() {
        //
    }

    public synchronized Connection getConnection() throws Exception {
        int capacity = getConnectionsInUse().remainingCapacity();
        if (capacity < 1) {
            throw new Exception("No available connections! Please, try again later!");
        }

        Connection connection = null;
        if (getFreeConnections().size() > 0) {
            connection = getFreeConnections().take();
        }

        if (connection == null) {
            connection = new ConnectionImpl();
        }

        if (!getConnectionsInUse().contains(connection)) {
            getConnectionsInUse().put(connection);
        }

        System.out.println("Getting " + connection + "\n");
        return connection;
    }

    synchronized void releaseConnection(Connection connection) {
        getConnectionsInUse().remove(connection);

        if (!getFreeConnections().contains(connection)) {
            getFreeConnections().add(connection);
        }

        System.out.println("Connection released and ready to be reused " + connection);
    }
}

4). And a demo class

public class _Main {

    public static void main(String[] args) {
        try {
            Connection conn1 = ConnectionPool.getPool().getConnection(); // new instance created
            Connection conn2 = ConnectionPool.getPool().getConnection(); // new instance created
            Connection conn3 = ConnectionPool.getPool().getConnection(); // new instance created

            conn1.release(); // instance released for reuse

            Connection conn4 = ConnectionPool.getPool().getConnection(); // receives conn1 instance

            conn2.release(); // instance released for reuse
            conn3.release(); // instance released for reuse

            Connection conn5 = ConnectionPool.getPool().getConnection(); // receives conn2 instance
            Connection conn6 = ConnectionPool.getPool().getConnection(); // receives conn3 instance

            Connection conn7 = ConnectionPool.getPool().getConnection(); // new instance created
            Connection conn8 = ConnectionPool.getPool().getConnection(); // new instance created
            Connection conn9 = ConnectionPool.getPool().getConnection(); // throws Exception (max number of connections is exceeded)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Output:

New connection with id=27ae7ee3-7171-4be0-853b-09de034824cc created. It took 2002ms.
Getting ConnectionImpl(id=27ae7ee3-7171-4be0-853b-09de034824cc)

New connection with id=cb7b87cc-6dee-4c78-9ed0-86b81109d3b0 created. It took 2003ms.
Getting ConnectionImpl(id=cb7b87cc-6dee-4c78-9ed0-86b81109d3b0)

New connection with id=1a79fcdf-bd5e-4849-93fe-c1d8f6dcc2f8 created. It took 2005ms.
Getting ConnectionImpl(id=1a79fcdf-bd5e-4849-93fe-c1d8f6dcc2f8)

Connection released and ready to be reused ConnectionImpl(id=27ae7ee3-7171-4be0-853b-09de034824cc)
Getting ConnectionImpl(id=27ae7ee3-7171-4be0-853b-09de034824cc)

Connection released and ready to be reused ConnectionImpl(id=cb7b87cc-6dee-4c78-9ed0-86b81109d3b0)
Connection released and ready to be reused ConnectionImpl(id=1a79fcdf-bd5e-4849-93fe-c1d8f6dcc2f8)
Getting ConnectionImpl(id=cb7b87cc-6dee-4c78-9ed0-86b81109d3b0)

Getting ConnectionImpl(id=1a79fcdf-bd5e-4849-93fe-c1d8f6dcc2f8)

New connection with id=52de5399-29ce-4ab7-bd21-e0098c64ab18 created. It took 2003ms.
Getting ConnectionImpl(id=52de5399-29ce-4ab7-bd21-e0098c64ab18)

New connection with id=072c0273-9a7a-4a3f-b85c-ccb0480164f4 created. It took 2003ms.
Getting ConnectionImpl(id=072c0273-9a7a-4a3f-b85c-ccb0480164f4)

java.lang.Exception: No available connections! Please, try again later!
	at com.smdev.creational.object_pool.example_1.ConnectionPool.getConnection(ConnectionPool.java:39)
	at com.smdev.creational.object_pool.example_1._Main.main(_Main.java:23)