Some useful JAVA features used in HikariPool source code (6)

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

1. AutoCloseable interface

1.1 Interface definition

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

1.2 Usage

The class that implements the AutoCloseable interface writes code according to the following syntax, and the close method is automatically called at the end of the try block without the need to display the call, so that there is no need to display the call to the close method in the finally block.

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

That is, you can write less finally block code.

1.3 Code examples

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

Output:

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

You can see that the order of execution is:

  1. Close the connection before exception handling
  2. Exception handling before finally block
  3. Close connection before finally block

That is, closing the connection is always performed first.

2. Make class copy by reflection

When there are many attributes of a certain class and copying is required, manually writing the copying method one by one is very cumbersome. At this time, a general method can be written by reflection to make a copy.

//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. High concurrency scenarios use ThreadLocalRandom to obtain random numbers

ThreadLocalRandom has higher performance than Math.Random () in high concurrency scenarios.

3.1 Examples

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

Test Results:

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

4. Call stack application

The code in HikariPool uses call stack information as follows:

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

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

The call stack information allows us to know who called method A, so that if we want to monitor who acquired the database connection without releasing it becomes possible.

4.1 Examples

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

Output:

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

According to the output, you can see that consumers who exceed the maximum connection time without releasing the connection can be monitored. Specifically:

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

After finding the specific call point, you can analyze and optimize.

5. JAVA dynamic proxy usage-avoid implementing all interfaces

Sometimes we have to implement or MOCK an interface, but do not need to implement all his methods (because there are too many methods, it is not necessary to implement), then it can be handled by dynamic proxy, the code in HikariPool is as follows:

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

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

Output:

run be called.
复制代码

6. Summary

Any open source Java code may have some Java features that we have not used before. Understanding these features helps us improve coding capabilities and output better implementation solutions. Therefore, in addition to reading and studying, read more source code.

end.


<-Thanks for the triple combo, like and attention on the left.


Related reading:
HikariPool source code (1) First acquaintance with
HikariPool source code (2) Design ideas borrow from
HikariPool source code (3) Dynamic scaling of resource pool
HikariPool source code (4) Resource status
HikariPool source code (5) Working threads and related tools


Java geek site: javageektour.com/

Guess you like

Origin juejin.im/post/5e9134cf51882573a033910c