13

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

只有当我可以覆盖调用线程的 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的资源类型,我们希望每个线程只有一个资源类型。

在使用类中定义:

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 垃圾收集的方法。

4

9 回答 9

14

如果你有敏感的性格,现在就移开视线。

我不希望这能很好地扩展。它有效地使系统中的线程数量增加了一倍。可能有一些可以接受的用例。

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使用另一个线程轮询isAlive来跟踪线程/资源。但这种解决方案是绝望的最后手段——对象可能最终会被检查得太频繁或太少。

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

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

于 2009-11-10T11:05:54.020 回答
6

用一个新的 Runnable 包装你的 Runnable

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

构造,并让它执行。

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

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

并调用传递您正在寻找的可运行文件的方法。

您可能还想考虑使用 Executor。使管理 Runable 和 Callable 变得更加容易。

如果你确实使用了 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...)

于 2009-11-09T10:40:49.430 回答
4

正常的 JDBC 实践是在获取它的方法块中关闭Connection(and Statementand )。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. 有关详细信息,请参阅 appserver 特定文档。

于 2009-11-09T16:49:25.793 回答
3

我真的不明白你为什么不使用传统的连接池。但我假设你有你的理由。

你有多少自由?由于一些 DI 框架确实支持对象生命周期和线程范围的变量(都很好地代理)。你能用其中之一吗?我认为 Spring 会开箱即用地完成这一切,而 Guice 需要一个第三方库来处理生命周期和线程范围。

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

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

于 2009-11-10T11:03:57.127 回答
1

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

于 2009-11-09T10:41:10.253 回答
1

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

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

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

于 2009-11-09T16:38:54.353 回答
1

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

根据评论更新

于 2009-11-10T10:59:49.947 回答
1

我们所做的是

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

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

于 2017-12-05T21:53:16.573 回答
0

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

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

于 2019-02-04T15:26:55.413 回答