HikariPool code source (5) threads de travail et outils associés

Java Geek | Auteur /   Kang Ran Yi Ye
Ceci est le 56ème article original de Java Geek

1. Planification des threads de travail dans HikariPool

Les threads de travail dans HikariPool sont planifiés via ThreadPoolExecutor, il y a 3 instances de ThreadPoolExecutor au total,

ThreadPoolExecutor Responsabilités Stratégie de traitement après surcharge
houseKeepingExecutorService Responsable
1. Réduire les connexions à la base de données dans la mise à l'échelle dynamique du pool de connexions à la base de données
2. Surveiller les fuites de connexion à la base de données
3. Surveiller les connexions à la base de données au-delà de la durée de vie maximale
Abandonner
addConnectionExecutor Responsable de la création de connexions à la base de données, y compris l'ajout de nouvelles connexions à la base de données lorsque le pool de connexions à la base de données évolue dynamiquement. Abandonner
closeConnectionExecutor Responsable de la fermeture de la connexion à la base de données. Répéter l'exécution jusqu'à réussite

1.1. houseKeepingExecutorService

Instanciation:

//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 Surveillance des fuites de connexion

//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 Mise à l'échelle dynamique du pool de connexions

//HikariPool.java
      // HouseKeeper是负载连接池动态伸缩的工作线程
      this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
复制代码

1.1.3 Surveillance de la durée de vie maximale des connexions à la base de données

         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

Instanciation:

//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;
   }
复制代码

Ajouter une connexion:

//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

Instanciation:

//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;
   }
复制代码

Fermez la connexion:

   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. Outils associés

Classe Responsabilités
ThreadPoolExecutor Exécuteur du fil
BlockingQueue La file d'attente de mémoire tampon utilisée par le pool de threads, la longueur de la file d'attente détermine le nombre maximal de threads de travail pouvant être mis en mémoire tampon
ThreadFactory Créer une fabrique de threads pour les threads de travail
ScheduledThreadPoolExecutor Un exécuteur de pool de threads qui prend en charge la planification planifiée peut spécifier une exécution différée et une exécution périodique. De cette façon, vous pouvez définir le temps de retard sur la durée de vie maximale pour contrôler si la connexion à la base de données dépasse la durée de vie maximale
DefaultThreadFactory La fabrique de threads par défaut implémentée dans HikariPool définit le nom du thread et définit le thread en tant que thread sprite
RejectedExecutionHandler Une interface de stratégie de traitement pour ajouter de nouveaux threads lorsque la file d'attente de threads dans l'exécuteur de thread est pleine
JeterOldestPolicy Jetez le plus ancien thread de travail non exécuté dans la file d'attente des threads et ajoutez un nouveau thread de travail, qui n'est pas utilisé dans HikariPool.
CallerRunsPolicy Répétez l'exécution jusqu'à ce qu'elle soit réussie, utilisée dans closeConnectionExecutor.
AbortPolicy Abandonnez le thread de travail qui dépasse la charge de la file d'attente des threads et lâchez une exception. Non utilisé dans HikariPool.
JeterPolitique Ignorez les threads de travail complexes qui dépassent la file d'attente des threads et ne faites rien. Utilisé dans houseKeepingExecutorService et houseKeepingExecutorService.

3. Classe de base

3.1 ThreadPoolExecutor

Constructeur:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
复制代码

Description des paramètres:

Paramètre Explication
int corePoolSize Le nombre minimum de threads à conserver dans l'exécuteur du pool de threads
int maximumPoolSize Le nombre maximal de threads autorisés par l'exécuteur du pool de threads
long keepAliveTime Temps de rétention des threads inutilisés, si ce temps est dépassé et que le nombre de threads est supérieur au nombre minimum de threads, le thread sera libéré
TimeUnit Unité de temps de rétention des threads inutilisés
BlockingQueue File d'attente du tampon de threads, l'effet de cette file d'attente sera affecté par maximumPoolSize et ne sera pas placé dans la file d'attente lorsque le nombre de threads sera suffisant.
ThreadFactory Interface de fabrique de threads pour générer des threads de travail
RejectedExecutionHandler Interface de stratégie de traitement une fois que le nombre de threads de travail est placé dans la file d'attente de cache dépasse la capacité de la file d'attente de cache

Il y a quelques points à noter ici:

  1. La file d'attente du tampon de threads doit être définie comme une file d'attente limitée pour éviter un débordement de mémoire dû à une augmentation infinie.
  2. Le nombre maximal de threads doit également être correctement contrôlé pour éviter de définir la valeur Integer.MAX_VALUE, pour les mêmes raisons que ci-dessus.
  3. La logique de traitement de la file d'attente du tampon de threads est affectée par corePoolSize et maximumPoolSize. Autrement dit, lorsqu'il y a suffisamment de threads disponibles, le thread de travail ne sera pas placé dans la file d'attente du tampon de threads.

Exemples:

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;
        }
    }
}
复制代码

Sortie:

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) {
复制代码

Vous pouvez emprunter sa capacité à retarder l'exécution des threads pour surveiller les fuites de connexion ou dépasser la durée de vie maximale.

4. Résumé

  1. Les classes liées à ThreadPoolExecutor et ScheduledThreadPoolExecutor sont les classes d'outils principales pour améliorer les performances d'exécution des threads et doivent être bien utilisées.
  2. Utilisez pleinement les outils de threads pour gérer les pools de ressources et organiser les threads de travail de manière raisonnable.

fin.


<-Merci pour le triple combo, comme et attention à gauche.


Lecture associée:
Code source HikariPool (1) Première connaissance du
code source HikariPool (2) Idées de conception empruntées au
code source HikariPool (3) Mise à l'échelle dynamique du pool de ressources
Code source HikariPool (4) Statut des ressources
Code source HikariPool (6) Quelques fonctionnalités JAVA utiles utilisées


Site geek Java: javageektour.com/

Je suppose que tu aimes

Origine juejin.im/post/5e9079b1e51d4546e347e191
conseillé
Classement