HikariPool source code (5) working threads and related tools

Java Geek | Author /   Kang Ran Yi Ye
This is the 56th original article by Java Geek

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:

  1. The thread buffer queue should be set as a bounded queue to avoid memory overflow due to infinite increase.
  2. The maximum number of threads should also be properly controlled to avoid setting to Integer.MAX_VALUE, for the same reasons as above.
  3. 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

  1. ThreadPoolExecutor and ScheduledThreadPoolExecutor related classes are the core tool classes to improve thread execution performance and should be used well.
  2. 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/

Guess you like

Origin juejin.im/post/5e9079b1e51d4546e347e191