如何强制 Java 线程关闭线程本地数据库连接
-
19-09-2019 - |
题
当使用线程本地数据库连接时,当线程存在时需要关闭连接。
只有当我可以重写调用线程的 run() 方法时,我才能做到这一点。即使这不是一个很好的解决方案,因为在退出时,我不知道该线程是否打开过连接。
这个问题实际上更普遍:如何强制线程在退出时调用线程局部对象的某些终结方法。
我查看了java 1.5的源码,发现线程局部映射设置为null,最终会导致垃圾回收调用 完成(), ,但我不想指望垃圾收集器。
为了确保关闭数据库连接,以下覆盖似乎是不可避免的:
@Override
public void remove() {
get().release();
super.remove();
}
在哪里 发布() 关闭数据库连接(如果已打开)。但我们不知道线程是否曾经使用过这个线程本地。如果该线程从未调用过 get() ,那么这里就浪费了精力: ThreadLocal.initialValue() 将被调用,将在此线程上创建地图等。
-
根据 Thorbjørn 的评论进一步澄清和示例:
java.lang.ThreadLocal 是绑定到线程的对象的工厂类型。这种类型有一个对象的 getter 和一个工厂方法(通常由用户编写)。当 getter 被调用时,只有当该线程之前从未调用过工厂方法时,它才会调用工厂方法。
使用 线程局部 允许开发人员将资源绑定到线程,即使线程代码是由第三方编写的。
例子:假设我们有一个名为的资源类型 我的风格 我们希望每个线程只有一个。
在using类中定义:private static threadlocal resource -factory = new threadLocal(){ @override protected myType initialValue(){返回new myType();}}}
在此类的本地上下文中使用:public void someMethod(){ MyType resource = resourceFactory.get();资源.useResource();}
得到() 可以打电话 初始值() 在调用线程的生命周期中仅出现一次。此时的一个实例是 我的风格 被实例化并绑定到该线程。随后调用 得到() 通过这个线程再次引用这个对象。
经典的用法示例是当 我的风格 是一些线程不安全的文本/日期/xml 格式化程序。
但这样的格式化程序通常不需要释放或关闭,数据库连接需要,我正在使用 java.lang.ThreadLocal 每个线程有一个数据库连接。
在我看来, java.lang.ThreadLocal 几乎是完美的。几乎是因为如果调用线程属于第三方应用程序,则无法保证资源关闭。
我需要你的大脑侍从:通过扩展 java.lang.ThreadLocal 我设法为每个线程绑定一个数据库连接,因为它是独占使用的 - 包括我无法修改或覆盖的线程。我设法确保连接关闭,以防线程因未捕获的异常而死亡。
如果线程正常退出,垃圾收集器会关闭连接(因为 我的风格 覆盖 完成())。事实上,它发生得很快,但这并不理想。
如果我有办法的话,就会有另一种方法 java.lang.ThreadLocal:
protected void release() throws Throwable {}
如果这个方法存在于 java.lang.ThreadLocal, ,由 JVM 在任何线程退出/死亡时调用,然后在我自己的重写中,我可以关闭我的连接(救赎者将来到 Zion)。
在没有这种方法的情况下,我正在寻找另一种方法来确认关闭。一种不依赖 JVM 垃圾收集的方法。
谢谢
解决方案
如果您性格敏感,请立即移开视线。
我不希望它能很好地扩展;它有效地使系统中的线程数量增加了一倍。可能在某些用例中这是可以接受的。
public class Estragon {
public static class Vladimir {
Vladimir() { System.out.println("Open"); }
public void close() { System.out.println("Close");}
}
private static ThreadLocal<Vladimir> HOLDER = new ThreadLocal<Vladimir>() {
@Override protected Vladimir initialValue() {
return createResource();
}
};
private static Vladimir createResource() {
final Vladimir resource = new Vladimir();
final Thread godot = Thread.currentThread();
new Thread() {
@Override public void run() {
try {
godot.join();
} catch (InterruptedException e) {
// thread dying; ignore
} finally {
resource.close();
}
}
}.start();
return resource;
}
public static Vladimir getResource() {
return HOLDER.get();
}
}
更好的错误处理等留给实施者作为练习。
您还可以查看跟踪中的线程/资源 ConcurrentHashMap
与另一个线程轮询 活着. 。但这种解决方案是绝望者的最后手段——对象可能最终会被检查得太频繁或太少。
我想不出还有什么不涉及仪器的。AOP 可能会起作用。
连接池将是我最喜欢的选择。
其他提示
用一个新的 Runnable 包装你的 Runnable
try {
wrappedRunnable.run();
} finally {
doMandatoryStuff();
}
构造,然后让其执行。
您甚至可以将其变成一种方法,例如:
Runnable closingRunnable(Runnable wrappedRunnable) {
return new Runnable() {
@Override
public void run() {
try {
wrappedRunnable.run();
} finally {
doMandatoryStuff();
}
}
};
}
并调用该方法并传入您正在寻找的可运行对象。
您可能还想考虑使用执行器。使管理 Runable 和 Callables 变得更加容易。
如果您确实使用 ExecutorService,则可以像这样使用它 executor.submit(closingRunnable(normalRunnable))
如果您知道您将关闭整个 ExecutorService 并希望在此时关闭连接,您可以设置一个线程工厂,该工厂也会在“所有任务完成并在执行器上调用关闭之后”执行关闭操作,例如:
ExecutorService autoClosingThreadPool(int numThreads) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // same as Executors.newFixedThreadPool
threadPool.setThreadFactory(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(closingRunnable(r)); // closes it when executor is shutdown
}
});
return threadPool;
}
关于 doMandatoryStuff 是否可以知道连接以前是否打开过,我想到的一件事是有第二个 ThreadLocal 只跟踪它是否打开(例如:当连接打开时,获取然后将 AtomicInteger 设置为 2,在清理时,检查它是否仍处于默认状态,例如 1...)
正常的 JDBC 做法是关闭 Connection
(和 Statement
和 ResultSet
)与您获取它的方法块完全相同。
在代码中:
Connection connection = null;
try {
connection = getConnectionSomehow();
// Do stuff.
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
ignoreOrLogItButDontThrowIt(e);
}
}
}
记住这一点,你的问题让我认为你的设计有问题。在最短的范围内获取并关闭这些昂贵的外部资源将使应用程序免受潜在的资源泄漏和崩溃的影响。
如果您的初衷是提高连接性能,那么您需要看看 连接池. 。例如,您可以使用 C3P0 用于此的 API。或者,如果它是一个 Web 应用程序,请使用应用程序服务器的内置连接池设施 DataSource
. 。有关详细信息,请参阅应用程序服务器特定文档。
我真的不明白为什么你不使用传统的连接池。但我假设你有你的理由。
你有多少自由?因为一些 DI 框架确实支持对象生命周期和线程范围的变量(所有这些都很好地代理)。你能使用其中之一吗?我认为 Spring 会开箱即用地完成这一切,而 Guice 需要一个第 3 方库来处理生命周期和线程范围。
接下来,您对 ThreadLocal 变量的创建或线程的创建有多少控制权?我猜你对 ThreadLocal 有完全的控制权,但对线程的创建没有限制?
您能否使用面向方面的编程来监视新的 Runnable 或扩展 run() 方法以包括清理的线程?您还需要扩展 ThreadLocal,以便它可以自行注册。
您必须打开连接一次,因此您还必须在同一位置处理关闭。根据您的环境,线程可能会被重用,并且您不能期望线程在应用程序关闭之前被垃圾收集。
我认为在一般情况下,除了经典的之外,没有好的解决方案:获取资源的代码必须负责关闭它。
在特定情况下,如果您要调用线程到这种程度,您可以在线程开始时将连接传递到您的方法,无论是使用采用自定义参数的方法还是通过某种形式的依赖注入。然后,由于您拥有提供连接的代码,因此您也拥有删除连接的代码。
基于注释的依赖注入可能在这里起作用,因为不需要连接的代码不会获得连接,因此不需要关闭,但听起来您的设计太过分了,无法改造类似的东西。
重写 ThreaedLocal 中的 get() 方法,以便它在子类上设置 List 属性。可以轻松查询此属性以确定是否已为特定线程调用 get() 方法。在这种情况下,您可以访问 ThreadLocal 来清理它。
已更新以回复评论
我们所做的是
@Override
public void run() {
try {
// ...
} finally {
resource.close();
}
}
基本上总是(可能打开然后)关闭通过线程的所有路径。如果它对那里的任何人有帮助:)
我正在看同样的问题。看起来到目前为止您必须使用 Finalize(),尽管它现在已被弃用。当任务被提交给某个执行器时,您永远不会知道线程何时确切退出,除非执行器显示给您,这意味着您在某种程度上可以控制执行器。
例如,如果您通过扩展 ThreadPoolExecutor 来构建 Executor,则可以重写 afterExecution() 和 Termination() 方法来完成它。前者用于线程异常退出,后者用于正常退出。