簡體   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