Design and Principle

The base case

HiKariCP as the default connection pool of SpringBoot2 framework, claimed to be the fastest running connection pool, database connection pool and the previous two mentioned thread pool and object pool, from the principle of design are based on the pooling idea, only in the implementation of their own characteristics; first or see the basic case of HiKariCP usage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class ConPool {
    private static HikariConfig buildConfig(){
        HikariConfig hikariConfig = new HikariConfig() ;
        // Basic configuration
        hikariConfig.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/javaisland");
        hikariConfig.setUsername("javaisland");
        hikariConfig.setPassword("javaisland");
        // Connection pool configuration
        hikariConfig.setPoolName("javaisland");
        hikariConfig.setMinimumIdle(4);
        hikariConfig.setMaximumPoolSize(8);
        hikariConfig.setIdleTimeout(600000L);
        return hikariConfig ;
    }
    public static void main(String[] args) throws Exception {
        // Build the data source
        HikariDataSource dataSource = new HikariDataSource(buildConfig()) ;
        // Get the connection
        Connection connection = dataSource.getConnection() ;
        // declare the SQL execution
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("SELECT count(1) num FROM javaisland") ;
        // Output the execution result
        if (resultSet.next()) {
            System.out.println("query-count-result: " + resultSet.getInt("num"));
        }
    }
}
  • HikariDataSource class : brings together information related to the description of the data source, such as configuration, connection pool, connection object, state management, etc..
  • HikariConfig class : maintain the configuration of the data source management, and parameter verification, such as userName, passWord, minIdle, maxPoolSize, etc..
  • HikariPool class: provides the core ability to manage connection pools and objects in the pool, and implements query methods for pool-related monitoring data.
  • ConcurrentBag class : abandon the blocking queue used in the conventional pool as a container, customize the concurrent container to store connection objects.
  • PoolEntry class : extends information about connection objects, such as status, time, etc., to facilitate tracking of these instantiated objects in the container.

Through the analysis of several core classes in the connection pool, you can also intuitively appreciate the design principles of the source code, and the previous summary of the application of the object pool has similarities, but different components of different developers in the implementation of the time, have their own abstract logic.

Loading logic

Through the configuration information to build the data source description, in the construction method based on the configuration and then to instantiate the connection pool, in the construction of HikariPool, instantiate the ConcurrentBag container object; the following and then analyze the implementation details from the source code level.

Container analysis

Container structure

Container ConcurrentBag class provides the PoolEntry type of connected object storage, as well as basic element management capabilities, object state description; although held by the HikariPool object pool class, but the actual logic of the operation is in the class.

Basic properties

The most central of these are sharedList shared collections, threadList thread-level caches, and handoffQueue instant queues.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Shared object collection that holds database connections
private final CopyOnWriteArrayList<T> sharedList;
// Cache of thread-level connection objects, which will be used first to avoid contention
private final ThreadLocal<List<Object>> threadList;
// Number of threads waiting to get a connection
private final AtomicInteger waiters;
// marker to close or not
private volatile boolean closed;
// Queue for instant connection processing, which assigns connections to waiting threads when they are available
private final SynchronousQueue<T> handoffQueue;

State Description

The IConcurrentBagEntry internal interface in the ConcurrentBag class, implemented by the PoolEntry class, which defines the state of the connection object.

  • STATE_NOT_IN_USE: unused, i.e., inactive.
  • STATE_IN_USE: in use.
  • STATE_REMOVED: deprecated.
  • STATE_RESERVED: reserved state, intermediate state, used when trying to evict a connected object.

Packaging objects

the basic ability of the container is used to store the connection object, while the management of the object requires a lot of extended tracking information to effectively complete the identification of various scenarios, at which point it is necessary to resort to the introduction of packaging classes;.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// the business really use the connection object
Connection connection;
// the most recent access time
long lastAccessed;
// Last borrowed time
long lastBorrowed;
// state description
private volatile int state = 0;
// Whether to evict or not
private volatile boolean evict;
// Scheduled task at the end of the life cycle
private volatile ScheduledFuture<? > endOfLife;
// Connection to the generated Statement object
private final FastList<Statement> openStatements;
// Pool object
private final HikariPool hikariPool;

Here it should be noted that the FastList class implements the List interface, customized for HiKariCP components, compared to the ArrayList class, for the pursuit of performance, in the management of the elements, to remove the many range checks.

Object management

Based on the conventional use of connection pools to see how the connection object is specifically managed, such as being lent, released, discarded, etc., as well as the state transition process of objects under these operations.

initialization

Above loading logic description, has been mentioned in the construction of the data source, will be instantiated according to the configuration of the connection pool, at the time of initialization, based on two core entry points to analyze the source code:

  • how many connection objects are instantiated
  • connection object conversion packaging object

checkFailFast method is executed in the connection pool construction, within which the MinIdle minimum idle number judgment is performed, and if it is greater than 0, a wrapper object is created and placed in the container.

1
2
3
4
5
6
7
public HikariPool(final HikariConfig config) ;
private void checkFailFast() {
    final PoolEntry poolEntry = createPoolEntry();
    if (config.getMinimumIdle() > 0) {
        connectionBag.add(poolEntry);
    }
}

Two issues need to be noted, the creation of the connection wrapper object, the initial state is 0 that is idle; in addition, although the case set the value of MinIdle=4, but here the judgment is greater than 0, but also only in the container in advance put a free object.

Borrowed Objects

When fetching the connection object from the pool, the borrow method in the container class is actually called.

1
2
public Connection HikariPool.getConnection(final long hardTimeout) throws SQLException ;
public T ConcurrentBag.borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException ;

When executing the borrow method, the following core steps and logic are involved.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
{
    // Iterating over the local thread cache
    final List<Object> list = threadList.get();
    for (int i = list.size() - 1; i >= 0; i--) {
       final Object entry = list.remove(i);
       final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
       if (bagEntry ! = null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { }
    }
    // Increase the number of waiting threads
    final int waiting = waiters.incrementAndGet();
    try {
        // Iterate through the Shared shared collection
        for (T bagEntry : sharedList) {
           if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { }
        }
        // Poll the handoff queue for a certain amount of time
        listener.addBagItem(waiting);
        timeout = timeUnit.toNanos(timeout);
        do {
           final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
        } 
    } finally {
        // Reduce the number of waiting threads
       waiters.decrementAndGet();
    }
}
  • first reverse iterate through the local thread cache and return the object if an idle connection exists; if not, look for the Shared collection.
  • Before traversing the Shared shared collection, it marks the number of waiting threads plus one, and returns it directly if there is an idle connection.
  • When there is no idle connection in the Shared shared collection either, then the current thread performs handoffQueue queue polling for a certain amount of time, possibly with the release of resources, or with newly added resources.

Note that here, when traversing the collection, the objects taken out are judged and updated for status, and if a free object is obtained, it is updated to IN_USE status and returned.

Releasing objects

When releasing a connected object from the pool, the requite method in the container class is actually called.

1
2
void HikariPool.recycle(final PoolEntry poolEntry) ;
public void ConcurrentBag.request(final T bagEntry) ;

When releasing the connection object, first update the object state to idle, then determine whether there is currently a waiting thread, in the borrow method waiting thread will enter a certain time polling, if not then the object into the local thread cache:.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public void requite(final T bagEntry) {
    // Update Status
    bagEntry.setState(STATE_NOT_IN_USE);
    // Waiting for the thread to judge
    for (int i = 0; waiters.get() > 0; i++) {
        if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) { }
    }
    // Local thread caching
    final List<Object> threadLocalList = threadList.get();
    if (threadLocalList.size() < 50) {
        threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
    }
}

Note that the state of the connection object involved here from use to NOT_IN_USE idle; borrow and requite as the two core methods in the connection pool, responsible for resource creation and recycling.

Although only part of the source code, but already enough to highlight the author’s ultimate pursuit of performance, such as: local thread cache, custom container types, FastList, etc.; can be commonly adopted must exist many reasons to support.