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

2022-09-04 06:47:01

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

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

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

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

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

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

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


根据Thorbjørn的评论进一步澄清和示例:

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

使用 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 垃圾回收的方法。


答案 1

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

我不指望这能很好地扩展;它有效地使系统中的线程数加倍。在某些用例中,它可能是可以接受的。

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

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


答案 2

用一个新的 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.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...)


推荐