Quelques fonctionnalités JAVA utiles utilisées dans le code source de HikariPool (6)

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

1. Interface à fermeture automatique

1.1 Définition de l'interface

public interface AutoCloseable {
    void close() throws Exception;
}
复制代码

1.2 Utilisation

La classe qui implémente l'interface AutoCloseable écrit le code selon la syntaxe suivante, et la méthode close sera automatiquement appelée à la fin du bloc try sans avoir besoin d'afficher l'appel, de sorte qu'il n'est pas nécessaire d'afficher la méthode close dans le bloc finally.

        try (Connection connection = new Connection()) {
            // do something
        }
复制代码

Autrement dit, vous pouvez écrire moins de code de bloc finalement.

1.3 Exemples de code

public class AutoCloseableDemo {

    public static void main(String[] args) {
        // 按照这个语法初始化连接,在使用完连接后(try块结束后)就会自动关闭
        try (Connection connection = new Connection(); Statement statement = connection.createStatement()) {
            // 模拟使用连接
            System.out.println("use connection.");
            // 模拟抛出异常,看看抛出异常后是否还会关闭数据库连接
            throw new Exception("use connection exception.");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 看看关闭连接和finally块的执行顺序
            System.out.println("enter finally block.");
        }
    }

    // 模拟数据库连接并实现AutoCloseable接口
    private static class Connection implements AutoCloseable {
        @Override
        // 注意,重载接口方法时,接口方法定义的异常也可以不抛出,这样在调用点就不需要捕捉异常
        public void close() throws Exception {
            System.out.println("close Connection be called.");
        }

        public Statement createStatement() {
            return new Statement();
        }
    }

    private static class Statement implements AutoCloseable {
        @Override
        // 注意,重载接口方法时,接口方法定义的异常也可以不抛出,这样在调用点就不需要捕捉异常
        public void close() throws Exception {
            System.out.println("close Statement be called.");
        }
    }
}
复制代码

Sortie:

use connection.
close Statement be called.
close Connection be called.
java.lang.Exception: use connection exception.
	at com.javageektour.hikaricp.demo.AutoCloseableTest.main(AutoCloseableTest.java:19)
enter finally block.
复制代码

Vous pouvez voir que l'ordre d'exécution est:

  1. Fermez la connexion avant la gestion des exceptions
  2. Gestion des exceptions avant de bloquer définitivement
  3. Fermer la connexion avant de finalement bloquer

Autrement dit, la fermeture de la connexion est toujours effectuée en premier.

2. Faites une copie de la classe par réflexion

Lorsqu'il existe de nombreux attributs d'une certaine classe et que la copie est requise, l'écriture manuelle de la méthode de copie une par une est très lourde. À ce stade, une méthode générale peut être écrite par réflexion pour faire une copie.

//HikariConfig.java
   public void copyStateTo(HikariConfig other)
   {
      // 遍历所有属性
      for (Field field : HikariConfig.class.getDeclaredFields()) {
         if (!Modifier.isFinal(field.getModifiers())) {
            field.setAccessible(true);
            try {
               // 拷贝属性值
               field.set(other, field.get(this));
            }
            catch (Exception e) {
               throw new RuntimeException("Failed to copy HikariConfig state: " + e.getMessage(), e);
            }
         }
      }

      other.sealed = false;
   }
复制代码

3. Les scénarios à concurrence élevée utilisent ThreadLocalRandom pour obtenir des nombres aléatoires

ThreadLocalRandom a des performances supérieures à Math.Random () dans les scénarios à concurrence élevée.

3.1 Exemples

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;

public class ThreadLocalRandomDemo {
    private static final int MAX_TIMES = 10000000;       // 获取次数
    private static final int THREAD_SIZE = 100;          // 并发线程数
    private static final int GET_MODE_RANDOM = 0;
    private static final int GET_MODE_THREAD_RANDOM = 1;
    private static volatile int getTimes = 0;            // 已获取次数
    private static long startTime = 0;
    private static CountDownLatch countDown = new CountDownLatch(THREAD_SIZE);

    public static void main(String[] args) throws Exception {
        startTime = System.currentTimeMillis();
        // 可修改getMode参数来测试 GET_MODE_RANDOM or GET_MODE_THREAD_RANDOM
        int getMode = GET_MODE_THREAD_RANDOM;
        for (int i = 0; i < THREAD_SIZE; i++) {
            new Thread(new GetRandomWorker(getMode)).start();
        }
        countDown.await();

        long costTime = System.currentTimeMillis() - startTime;
        System.out.println((getMode == GET_MODE_RANDOM ? "Random" : "ThreadRandom") + " costTime: " + costTime);
    }

    private static class GetRandomWorker implements Runnable {
        private int getMode;
        public GetRandomWorker(int getMode) {
            this.getMode = getMode;
        }
        @Override
        public void run() {
            while(true) {
                if (getMode == GET_MODE_RANDOM) {
                    int i = (int) (Math.random() * 10);
                } else {
                    int i = ThreadLocalRandom.current().nextInt(10);
                }
                getTimes++;
                if (getTimes > MAX_TIMES) {
                    countDown.countDown();
                    break;
                }
            }
        }
    }
}
复制代码

Résultats des tests:

Random costTime: 2303
ThreadRandom costTime: 989
复制代码

4. Application de pile d'appels

Le code dans HikariPool utilise les informations de pile d'appels comme suit:

//TestElf.java
   public static HikariConfig newHikariConfig()
   {
      final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2];

      String poolName = callerStackTrace.getMethodName();
      if ("setup".equals(poolName)) {
         poolName = callerStackTrace.getClassName();
      }
复制代码

Les informations de la pile d'appels nous permettent de savoir qui a appelé la méthode A, de sorte que si nous voulons surveiller qui a acquis la connexion à la base de données sans la libérer, cela devient possible.

4.1 Exemples

import java.util.concurrent.*;

public class StackTraceDemo {
    // 允许消费者持有数据库连接的最大时间,超过这个时间则认为连接泄漏。 (为方便测试,这里设置的值并不大)
    private static final int MAX_HOLD_TIME_SECONDS = 10;

    public static void main(String[] args) {
        // 模拟消费者正常使用连接
        Consumer consumerA = new Consumer(5);
        consumerA.exec();

        // 模拟消费者长时间不释放连接,监控程序将监控到是谁没有释放连接
        Consumer consumerB = new Consumer(MAX_HOLD_TIME_SECONDS + 100);
        consumerB.exec();
    }

    private static void quietSleep(long senonds) {
        try {
            Thread.sleep(senonds * 1000);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
        }
    }

    // 模拟消费者
    private static class Consumer {
        private int execCostTimeSeconds;  // 模拟执行耗时
        public Consumer(int execCostTimeSeconds) {
            this.execCostTimeSeconds = execCostTimeSeconds;
        }

        public void exec() {
            Connection conn = null;
            try {
                System.out.println("Consumer start... " + this);
                conn = ConnectionFactory.getConnection();
                quietSleep(execCostTimeSeconds); // 模拟执行耗时操作,如果超过最大允许持有连接时间,则会被监控到
                System.out.println("Consumer end. " + this);
            } finally {
                conn.close();
            }
        }
    }

    // 连接工厂
    private static class ConnectionFactory {
        public static Connection getConnection() {
            // 获取调用栈信息,这样可以直到是谁使用了连接
            StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

            // 通过一个延迟任务去执行连接泄漏监控任务
            ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, new DefatulyThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
            executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
            executor.setRemoveOnCancelPolicy(true);
            ScheduledFuture<?> scheduledFuture =  executor.schedule(new ConnectionLeakMonitor(stackTraceElements), MAX_HOLD_TIME_SECONDS, TimeUnit.SECONDS);
            // 入参的目的是当连接正常关闭时,可以终止监控任务
            return new Connection(scheduledFuture);
        }
    }

    // 线程工厂
    private static class DefatulyThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            // 简单实现
            return new Thread(r);
        }
    }

    // 数据库连接泄漏监控者,如果超过最大持有时间未关闭连接,则认为是连接泄漏
    private static class ConnectionLeakMonitor implements Runnable {
        private StackTraceElement[] stackTraceElements;
        public ConnectionLeakMonitor(StackTraceElement[] stackTraceElements) {
            this.stackTraceElements = stackTraceElements;
        }

        @Override
        public void run() {
            if (stackTraceElements != null) {
                // 这里仅仅打印调用者堆栈信息,实际应用时可上报到监控平台中
                for (StackTraceElement stackTraceElement: stackTraceElements) {
                    System.out.println(stackTraceElement.toString());
                }
            }
        }
    }

    // 模拟数据库连接,实际应用中对应连接代理类
    private static class Connection {
        private ScheduledFuture<?> scheduledFuture;
        public Connection(ScheduledFuture<?> scheduledFuture) {
            this.scheduledFuture = scheduledFuture;
        }

        public void close() {
            System.out.println("connection be closed.");
            // 如果连接正常关闭,则取消监控任务
            scheduledFuture.cancel(false);
        }
    }
}
复制代码

Sortie:

Consumer start... com.javageektour.hikaricp.demo.StackTraceDemo$Consumer@28d93b30
Consumer end. com.javageektour.hikaricp.demo.StackTraceDemo$Consumer@28d93b30
connection be closed.
Consumer start... com.javageektour.hikaricp.demo.StackTraceDemo$Consumer@14ae5a5
java.lang.Thread.getStackTrace(Thread.java:1552)
com.javageektour.hikaricp.demo.StackTraceDemo$ConnectionFactory.getConnection(StackTraceDemo.java:53)
com.javageektour.hikaricp.demo.StackTraceDemo$Consumer.exec(StackTraceDemo.java:42)
com.javageektour.hikaricp.demo.StackTraceDemo.main(StackTraceDemo.java:22)
复制代码

Selon la sortie, vous pouvez voir que les consommateurs qui dépassent le temps de connexion maximum sans libérer la connexion peuvent être surveillés.

com.javageektour.hikaricp.demo.StackTraceDemo$Consumer.exec(StackTraceDemo.java:42)
复制代码

Après avoir trouvé le déclencheur spécifique, vous pouvez analyser et optimiser.

5. Utilisation du proxy dynamique JAVA - évitez d'implémenter toutes les interfaces

Parfois, nous devons implémenter ou MOCKER une interface, mais nous n'avons pas besoin d'implémenter toutes ses méthodes (car il y a trop de méthodes, il n'est pas nécessaire de l'implémenter), alors elle peut être gérée par un proxy dynamique. Le code dans HikariPool est le suivant:

//ProxyConnection.java
   private static final class ClosedConnection
   {
      static final Connection CLOSED_CONNECTION = getClosedConnection();

      private static Connection getClosedConnection()
      {
         // 这里是个特殊写法,因为InvocationHandler只有一个接口,跟写一个内部内一样
         InvocationHandler handler = (proxy, method, args) -> {
            final String methodName = method.getName();
            if ("isClosed".equals(methodName)) {
               return Boolean.TRUE;
            }
            else if ("isValid".equals(methodName)) {
               return Boolean.FALSE;
            }
            if ("abort".equals(methodName)) {
               return Void.TYPE;
            }
            if ("close".equals(methodName)) {
               return Void.TYPE;
            }
            else if ("toString".equals(methodName)) {
               return ClosedConnection.class.getCanonicalName();
            }

            throw new SQLException("Connection is closed");
         };

         return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);
      }
   }
复制代码

5.1 Exemple de vérification

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class InvocationHandlerDemo {

    public static void main(String[] args) {
        Dog dog = getDog();
        dog.run();
        dog.jump();  // 并没有实现该方法,啥也不会做,也不会报错
    }

    private static Dog getDog()
    {
        // 这里是个特殊写法,因为InvocationHandler只有一个接口,跟写一个内部内一样
        InvocationHandler handler = (proxy, method, args) -> {
            final String methodName = method.getName();
            // 只想实现必要的方法
            if ("run".equals(methodName)) {
                System.out.println("run be called.");
            }
            return null;
        };

        return (Dog) Proxy.newProxyInstance(Dog.class.getClassLoader(), new Class[] { Dog.class }, handler);
    }

    private static interface Dog {
        void run();
        void jump();
    }
}
复制代码

Sortie:

run be called.
复制代码

6. Résumé

Tout code Java open source peut avoir des fonctionnalités Java que nous n'avons pas utilisées auparavant. La compréhension de ces fonctionnalités nous aide à améliorer les capacités de codage et à produire de meilleures solutions d'implémentation. Par conséquent, en plus de lire et d'étudier, lisez plus de code source.

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 (5) Fils de travail et outils associés


Site geek Java: javageektour.com/

Je suppose que tu aimes

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