1. Worker thread scheduling in HikariPool
The working threads in HikariPool are scheduled through ThreadPoolExecutor, there are 3 ThreadPoolExecutor instances in total,
ThreadPoolExecutor | Responsibilities | Treatment strategy after overload |
---|---|---|
houseKeepingExecutorService | Responsible 1. Reduce database connections in the dynamic scaling of the database connection pool 2. Monitor database connection leaks 3. Monitor database connections beyond the maximum lifetime |
abandon |
addConnectionExecutor | Responsible for creating database connections, including adding new database connections when the database connection pool scales dynamically. | abandon |
closeConnectionExecutor | Responsible for closing the database connection. | Repeat execution until successful |
1.1. houseKeepingExecutorService
Instantiation:
//HikariPool.java
private ScheduledExecutorService initializeHouseKeepingExecutorService()
{
if (config.getScheduledExecutor() == null) {
final ThreadFactory threadFactory = Optional.ofNullable(config.getThreadFactory()).orElseGet(() -> new DefaultThreadFactory(poolName + " housekeeper", true));
final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
executor.setRemoveOnCancelPolicy(true);
return executor;
}
else {
return config.getScheduledExecutor();
}
}
复制代码
1.1.1 Monitoring connection leaks
//HikariPool.java
this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
复制代码
//ProxyLeakTaskFactory.java
private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
ProxyLeakTask task = new ProxyLeakTask(poolEntry);
// executorService就是houseKeepingExecutorService
task.schedule(executorService, leakDetectionThreshold);
return task;
}
复制代码
//ProxyLeakTask.java
ProxyLeakTask(final PoolEntry poolEntry)
{
this.exception = new Exception("Apparent connection leak detected");
this.threadName = Thread.currentThread().getName();
this.connectionName = poolEntry.connection.toString();
}
public void run()
{
isLeaked = true;
final StackTraceElement[] stackTrace = exception.getStackTrace();
final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
System.arraycopy(stackTrace, 5, trace, 0, trace.length);
exception.setStackTrace(trace);
// 下面是监控到连接泄漏的处理,这里只是记录到日志中,如果通过一个接口处理,并可以让使用者动态实现会更灵活
LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception);
}
复制代码
1.1.2 Connection pool dynamic scaling
//HikariPool.java
// HouseKeeper是负载连接池动态伸缩的工作线程
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
复制代码
1.1.3 Monitoring the maximum lifetime of database connections
final long maxLifetime = config.getMaxLifetime();
if (maxLifetime > 0) {
// variance up to 2.5% of the maxlifetime
final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
final long lifetime = maxLifetime - variance;
poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
() -> {
if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
addBagItem(connectionBag.getWaitingThreadCount());
}
},
lifetime, MILLISECONDS));
}
复制代码
1.2. addConnectionExecutor
Instantiation:
//HikariPool.java
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
复制代码
//UtilityElf.java
public static ThreadPoolExecutor createThreadPoolExecutor(final BlockingQueue<Runnable> queue, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy)
{
if (threadFactory == null) {
threadFactory = new DefaultThreadFactory(threadName, true);
}
ThreadPoolExecutor executor = new ThreadPoolExecutor(1 /*core*/, 1 /*max*/, 5 /*keepalive*/, SECONDS, queue, threadFactory, policy);
executor.allowCoreThreadTimeOut(true);
return executor;
}
复制代码
Add connection:
//HikariPool.java
public void addBagItem(final int waiting)
{
final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
if (shouldAdd) {
addConnectionExecutor.submit(poolEntryCreator);
}
}
// 连接词动态伸缩增加连接
private synchronized void fillPool()
{
final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
- addConnectionQueue.size();
for (int i = 0; i < connectionsToAdd; i++) {
addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
}
}
复制代码
1.3. closeConnectionExecutor
Instantiation:
//HikariPool.java
this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
复制代码
//UtilityElf.java
public static ThreadPoolExecutor createThreadPoolExecutor(final int queueSize, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy)
{
if (threadFactory == null) {
threadFactory = new DefaultThreadFactory(threadName, true);
}
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(queueSize);
ThreadPoolExecutor executor = new ThreadPoolExecutor(1 /*core*/, 1 /*max*/, 5 /*keepalive*/, SECONDS, queue, threadFactory, policy);
executor.allowCoreThreadTimeOut(true);
return executor;
}
复制代码
Close the connection:
void closeConnection(final PoolEntry poolEntry, final String closureReason)
{
if (connectionBag.remove(poolEntry)) {
final Connection connection = poolEntry.close();
closeConnectionExecutor.execute(() -> {
quietlyCloseConnection(connection, closureReason);
if (poolState == POOL_NORMAL) {
fillPool();
}
});
}
}
复制代码
2. Related tools
class | Responsibilities |
---|---|
ThreadPoolExecutor | Thread executor |
BlockingQueue | The buffer queue used by the thread pool, the queue length determines the maximum number of worker threads that can be buffered |
ThreadFactory | Create a thread factory for worker threads |
ScheduledThreadPoolExecutor | A thread pool executor that supports scheduled scheduling can specify delayed execution and periodic execution. In this way, you can set the delay time to the maximum life time to monitor whether the database connection exceeds the maximum life time |
DefaultThreadFactory | The default thread factory implemented in HikariPool sets the thread name and sets the thread as a sprite thread |
RejectedExecutionHandler | A processing strategy interface for adding new threads when the thread queue in the thread executor is full |
DiscardOldestPolicy | Abandon the oldest unexecuted worker thread in the thread queue and add a new worker thread, which is not used in HikariPool. |
CallerRunsPolicy | Repeat execution until successful, used in closeConnectionExecutor. |
AbortPolicy | Abandon the worker thread that exceeds the load of the thread queue and throw an exception. Not used in HikariPool. |
DiscardPolicy | Ignore complex worker threads that exceed the thread queue and do nothing. Used in houseKeepingExecutorService and houseKeepingExecutorService. |
3. Core class
3.1 ThreadPoolExecutor
Constructor:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
复制代码
Parameter Description:
parameter | Explanation |
---|---|
int corePoolSize | The minimum number of threads to keep in the thread pool executor |
int maximumPoolSize | The maximum number of threads allowed by the thread pool executor |
long keepAliveTime | Retention time of unused threads, if this time is exceeded and the number of threads is greater than the minimum number of threads, the thread will be released |
TimeUnit | Unused thread retention time unit |
BlockingQueue | Thread buffer queue, the effect of this queue will be affected by maximumPoolSize, and will not be put into the queue when the number of threads is sufficient. |
ThreadFactory | Thread factory interface for generating worker threads |
RejectedExecutionHandler | Processing strategy interface after the number of worker threads is placed in the cache queue exceeds the capacity of the cache queue |
There are a few points to note here:
- The thread buffer queue should be set as a bounded queue to avoid memory overflow due to infinite increase.
- The maximum number of threads should also be properly controlled to avoid setting to Integer.MAX_VALUE, for the same reasons as above.
- The processing logic of the thread buffer queue is affected by corePoolSize and maximumPoolSize. Simply put, when there are enough available threads, the worker thread will not be put into the thread buffer queue.
example:
import java.util.concurrent.*;
import static java.util.concurrent.ThreadPoolExecutor.*;
public class ThreadPoolExecutorTest {
private static int runableNum = 1;
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue(3);
// 修改maximumPoolSize和maximumPoolSize的大小可以看到对queue处理逻辑的影响
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 300, TimeUnit.SECONDS,
queue, new DefaultThreadFactory(), new DefaultDiscardPolicy());
while(true) {
System.out.println("runableNum: " + runableNum);
executor.execute(new DefaultRunnable("id-" + runableNum));
runableNum++;
quietlySleep(500);
}
}
private static void quietlySleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
private static class DefaultRunnable implements Runnable {
private String name;
public DefaultRunnable(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Runnable-" + name + " run.");
quietlySleep(3000);
}
public String getName() {
return this.name;
}
}
private static class DefaultDiscardPolicy extends DiscardPolicy {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
super.rejectedExecution(r, e);
if (r instanceof DefaultRunnable) {
DefaultRunnable defaultRunnable = (DefaultRunnable)r;
System.out.println("Runnable-" + defaultRunnable.getName() + " be discard.");
}
}
}
private static class DefaultThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
}
}
复制代码
Output:
runableNum: 1
Runnable-id-1 run.
runableNum: 2
runableNum: 3
runableNum: 4
runableNum: 5
Runnable-id-5 run.
runableNum: 6
Runnable-id-6 run.
Runnable-id-2 run.
runableNum: 7
runableNum: 8
Runnable-id-8 be discard.
runableNum: 9
Runnable-id-9 be discard.
runableNum: 10
Runnable-id-10 be discard.
Runnable-id-3 run.
runableNum: 11
Runnable-id-4 run.
runableNum: 12
Runnable-id-7 run.
runableNum: 13
runableNum: 14
Runnable-id-14 be discard.
复制代码
3.2 ScheduledThreadPoolExecutor
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
复制代码
You can borrow its ability to delay the execution of threads to monitor connection leaks or exceed the maximum lifetime.
4. Summary
- ThreadPoolExecutor and ScheduledThreadPoolExecutor related classes are the core tool classes to improve thread execution performance and should be used well.
- Make full use of thread tools to manage resource pools and arrange working threads reasonably.
end.
<-Thanks for the triple combo, like and attention on the left.
Related reading:
HikariPool source code (1)
Get acquainted with HikariPool source code (2) Design ideas borrow from
HikariPool source code (3) Resource pool dynamic scaling
HikariPool source code (4) Resource status
HikariPool source code (6) Some useful JAVA features
Java geek site: javageektour.com/