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

时间:2009-11-09 10:29:54

标签: java thread-local resource-disposal

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

这只有在我可以覆盖调用线程的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 resourceFactory = new ThreadLocal(){         @override         protected MyType initialValue(){             返回新的MyType();         }     }

在此课程的本地环境中使用:     public void someMethod(){         MyType资源= resourceFactory.get();         resource.useResource();     }

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

经典用法示例是 MyType 是一些线程不安全的text / date / 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垃圾收集的方法。

由于

9 个答案:

答案 0 :(得分:13)

如果你是一个敏感的性格,现在就把目光移开。

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

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可能有用。

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

答案 1 :(得分:6)

使用带有

的新Runnable包装Runnable
try {
  wrappedRunnable.run();
} finally {
  doMandatoryStuff();
}

构造,而不是执行。

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

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

并调用该方法传入您正在寻找的runnable。

您可能还想考虑使用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 ...)

答案 2 :(得分:4)

正常的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应用程序,请使用appserver的内置连接池设施,其风格为DataSource。有关详细信息,请参阅appserver特定文档。

答案 3 :(得分:3)

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

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

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

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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

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

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

基于注释的依赖注入可能在这里工作,因为不需要连接的代码不会得到一个,因此不需要关闭,但听起来你的设计太过于改进某些东西那样的。

答案 6 :(得分:1)

重写ThreaedLocal中的get()方法,以便在子类上设置List属性。 可以轻松查询此属性以确定是否已为特定线程调用get()方法。然后你可以访问ThreadLocal来清理它。

更新以回复评论

答案 7 :(得分:1)

我们做的是

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

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

答案 8 :(得分:0)

我在看同样的问题。到目前为止,您似乎必须使用finalize(),尽管现在已不推荐使用它。当任务提交给某个执行器时,除非执行器向您显示,否则您永远都不知道线程何时确切退出,这意味着您可以对执行器进行一定程度的控制。

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

相关问题