public interface AutoCloseable { void close() throws Exception; } 复制代码
实现了AutoCloseable接口的类,按照如下语法编写代码,在try块结束会自动调用close方法,而不需要显示调用,这样就不需要在finally块中显示调用close方法。
try (Connection connection = new Connection()) { // do something } 复制代码
也就是可以少写一个finally块代码。
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."); } } } 复制代码
输出:
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. 复制代码
可以看到执行顺序是:
即关闭连接总是最先执行。
当某个类的属性很多,需要拷贝时,人工一个个写拷贝方法很繁琐,此时可以通过反射写一个通用方法来做拷贝。
//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; } 复制代码
高并发场景下ThreadLocalRandom获取随机数的性能比Math.Random()高。
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; } } } } } 复制代码
测试结果:
Random costTime: 2303 ThreadRandom costTime: 989 复制代码
在HikariPool中有代码使用了调用栈信息,如下:
//TestElf.java public static HikariConfig newHikariConfig() { final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2]; String poolName = callerStackTrace.getMethodName(); if ("setup".equals(poolName)) { poolName = callerStackTrace.getClassName(); } 复制代码
调用栈信息使得我们可以知道A方法是被谁调用的,这样如果我们想监控是谁获取了数据库连接,而没有释放就变得可能。
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); } } } 复制代码
输出:
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) 复制代码
根据输出内容可以看到,超出最大持有连接时间的消费者没有释放连接可以被监控到,具体的就是:
com.javageektour.hikaricp.demo.StackTraceDemo$Consumer.exec(StackTraceDemo.java:42) 复制代码
找到具体的调用点之后,就可以分析优化了。
有的时候我们要实现或者MOCK一个接口,但是又不需要实现他的所有方法(因为方法太多,都实现没有必要),那么就可以通过动态代理来处理,HikariPool中的代码如下:
//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); } } 复制代码
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(); } } 复制代码
输出:
run be called. 复制代码
任何开源Java代码中都可能有一些我们不曾使用过的Java特性,了解这些特性有助于我们提高编码能力和输出更优秀的实现方案。因此除了看书学习,多读读源码吧。
end.
<--感谢三连击,左边点赞和关注。