繁体   English   中英

如何强制 Java 线程关闭线程本地数据库连接

[英]How to force a Java thread to close a thread-local database connection

当使用线程本地数据库连接时,当线程存在时需要关闭连接。

只有当我可以覆盖调用线程的 run() 方法时,我才能做到这一点。 即使这不是一个很好的解决方案,因为在退出时,我不知道该线程是否曾经打开过连接。

问题实际上更普遍:如何强制线程在退出时调用线程本地对象的某些终结方法。

我查看了java 1.5的源码,发现线程本地映射设置为null,最终会导致垃圾收集调用finalize() ,但是我不想指望垃圾收集器。

为了确保关闭数据库连接,以下覆盖似乎是不可避免的:

@Override 
public void remove() {
    get().release(); 
    super.remove(); 
}

其中release()关闭数据库连接,如果它已经打开。 但是我们不知道线程是否曾经使用过这个线程本地。 如果 get() 从来没有被这个线程调用过,那么这里就很浪费精力: ThreadLocal.initialValue()将被调用,一个映射将在这个线程上创建,等等。


根据 Thorbjørn 的评论进一步说明和示例:

java.lang.ThreadLocal是绑定到线程的对象的一种工厂类型。 此类型具有对象的 getter 和工厂方法(通常由用户编写)。 当 getter 被调用时,只有在此线程之前从未调用过它时,它才会调用工厂方法。

使用ThreadLocal允许开发人员将资源绑定到线程,即使线程代码是由第三方编写的。

示例:假设我们有一个名为MyType的资源类型,我们希望每个线程只有一个。

在 using 类中定义:

private static ThreadLocal<MyType> resourceFactory = new ThreadLocal<MyType>(){
    @override
    protected MyType initialValue(){
        return new MyType();
    }
}

在此类的本地上下文中使用:

public void someMethod(){
    MyType resource = resourceFactory.get();
    resource.useResource();
}

get()只能在调用线程的生命周期中调用initialValue()一次。 此时MyType 的一个实例被实例化并绑定到这个线程。 此线程对get()的后续调用再次引用此对象。

经典用法示例是MyType是一些线程不安全的文本/日期/xml 格式化程序。

但是这样的格式化程序通常不需要释放或关闭,数据库连接需要,我使用java.lang.ThreadLocal每个线程有一个数据库连接。

在我看来, java.lang.ThreadLocal几乎是完美的。 几乎是因为如果调用线程属于第三方应用程序,则无法保证资源关闭。

我需要你的大脑乡绅:通过扩展java.lang.ThreadLocal我设法为每个线程绑定一个数据库连接,因为它是独占使用 - 包括我无法修改或覆盖的线程。 我设法确保连接关闭,以防线程因未捕获的异常而死亡。

在正常线程退出的情况下,垃圾收集器关闭连接(因为MyType覆盖了finalize() )。 事实上,它发生得很快,但这并不理想。

如果我有我的方式, java.lang.ThreadLocal上会有另一种方法:

protected void release() throws Throwable {}

如果这个方法存在于java.lang.ThreadLocal 上,在任何线程退出/死亡时由 JVM 调用,那么在我自己的覆盖中,我可以关闭我的连接(并且救赎者会来到锡安)。

在没有这种方法的情况下,我正在寻找另一种方法来确认关闭。 一种不依赖于 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();
  }
}

更好的错误处理等留给实现者作为练习。

您还可以查看使用另一个线程轮询isAlive来跟踪ConcurrentHashMap的线程/资源。 但该解决方案是绝望的最后手段 - 对象最终可能会被检查得太频繁或太少。

我想不出任何不涉及仪器的东西。 AOP 可能会起作用。

连接池将是我最喜欢的选择。

用一个新的 Runnable 包裹你的 Runnable

try {
  wrappedRunnable.run();
} finally {
  doMandatoryStuff();
}

构造,并让 THAT 改为执行。

你甚至可以把它变成一个方法,例如:

  Runnable closingRunnable(Runnable wrappedRunnable) {
    return new Runnable() {
      @Override
      public void run() {
        try {
          wrappedRunnable.run();
        } finally {
          doMandatoryStuff();
        }
      }
    };
  }

并调用该方法传入您正在寻找的可运行对象。

您可能还想考虑使用 Executor 代替。 使管理 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 (以及StatementResultSet )。

在代码中:

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 需要一个 3rd 方库来处理生命周期和线程范围。

接下来,您对 ThreadLocal 变量的创建或线程的创建有多少控制权? 我猜你对 ThreadLocal 有完全的控制权,但对线程的创建没有限制?

您能否使用面向方面的编程来监视新的 Runnable 或扩展 run() 方法以包括清理的线程? 您还需要扩展 ThreadLocal 以便它可以自行注册。

您必须打开连接一次,因此您还必须在同一位置处理关闭。 根据您的环境,线程可能会被重用,并且您不能期望在应用程序关闭之前对线程进行垃圾回收。

我认为在一般情况下,除了经典之外,没有好的解决方案:获取资源的代码必须负责关闭它。

在特定情况下,如果您以这种程度调用线程,您可以在线程开始时将连接传递到您的方法,使用带有自定义参数的方法或通过某种形式的依赖注入。 然后,由于您拥有提供连接的代码,因此您拥有删除它的代码。

基于注解的依赖注入可能在这里工作,因为不需要连接的代码不会得到连接,因此不需要关闭,但听起来你的设计太远了,无法改造这样的东西。

覆盖 ThraedLocal 中的 get() 方法,以便它在子类上设置 List 属性。 可以轻松查询此属性以确定是否为特定线程调用了 get() 方法。 在这种情况下,您可以访问 ThreadLocal 来清理它。

更新以回应评论

我们所做的是

@Override
public void run() {
  try {
    // ...
  } finally {
    resource.close();
  }
}

基本上总是(可能打开然后)关闭它通过线程的所有路径。 如果它可以帮助那里的任何人:)

我在看同样的问题。 看起来到目前为止您必须使用 finalize(),尽管它现在已被弃用。 当任务提交给某个 Executor 时,您永远不会知道线程何时确切退出,除非 Executor 向您显示,这意味着您在某种程度上可以控制 Executor。

例如,如果您通过扩展 ThreadPoolExecutor 来构建 Executor,您可以覆盖 afterExecution() 和 terminate() 方法来完成它。 前者为线程异常退出,后者为正常退出。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM