1. AutoCloseable接口
1.1 接口定义
public interface AutoCloseable {
void close() throws Exception;
}
复制代码
1.2 用法
实现了AutoCloseable接口的类,按照如下语法编写代码,在try块结束会自动调用close方法,而不需要显示调用,这样就不需要在finally块中显示调用close方法。
try (Connection connection = new Connection()) {
// do something
}
复制代码
也就是可以少写一个finally块代码。
1.3 代码示例
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.
复制代码
可以看到执行顺序是:
- 关闭连接在异常处理前
- 异常处理在finally块前
- 关闭连接在finally块前
即关闭连接总是最先执行。
2. 通过反射来做类拷贝
当某个类的属性很多,需要拷贝时,人工一个个写拷贝方法很繁琐,此时可以通过反射写一个通用方法来做拷贝。
//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. 高并发场景使用ThreadLocalRandom来获取随机数
高并发场景下ThreadLocalRandom获取随机数的性能比Math.Random()高。
3.1 例子
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
复制代码
4. 调用栈应用
在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方法是被谁调用的,这样如果我们想监控是谁获取了数据库连接,而没有释放就变得可能。
4.1 例子
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)
复制代码
找到具体的调用点之后,就可以分析优化了。
5. JAVA动态代理用法-避免实现全部接口
有的时候我们要实现或者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);
}
}
复制代码
5.1 验证例子
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.
复制代码
6. 总结
任何开源Java代码中都可能有一些我们不曾使用过的Java特性,了解这些特性有助于我们提高编码能力和输出更优秀的实现方案。因此除了看书学习,多读读源码吧。
end.
<--感谢三连击,左边点赞和关注。
相关阅读:
HikariPool源码(一)初识
HikariPool源码(二)设计思想借鉴
HikariPool源码(三)资源池动态伸缩
HikariPool源码(四)资源状态
HikariPool源码(五)工作线程以及相关工具类
Java极客站点: javageektour.com/