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が呼び出されると、このスレッドによって以前に呼び出されたことがない場合にのみ、ファクトリメソッドが呼び出されます。

ThreadLocalを使用すると、開発者は、スレッドコードがサードパーティによって作成された場合でも、リソースをスレッドにバインドできます。

例: MyTypeというリソースタイプがあり、スレッドごとに1つだけにしたいとします。

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がスレッドセーフでないtext / date/xmlフォーマッターである場合です。

しかし、そのようなフォーマッターは通常、解放または閉じる必要はありません。データベース接続は解放する必要があります。私はjava.lang.ThreadLocalを使用して、スレッドごとに1つのデータベース接続を確立しています。

私の見方では、java.lang.ThreadLocalはそのためにほぼ完璧です。呼び出し元のスレッドがサードパーティのアプリケーションに属している場合、リソースのクローズを保証する方法がないためです。

私はあなたの頭脳の従者が必要です:java.lang.ThreadLocalを拡張することによって、私はスレッドごとに1つのデータベース接続をバインドすることができました。これは、変更またはオーバーライドできないスレッドを含む排他的な使用法です。キャッチされない例外でスレッドが停止した場合に備えて、接続が確実に閉じられるようにしました。

通常のスレッド終了の場合、ガベージコレクターは接続を閉じます(MyTypeがfinalize()をオーバーライドするため)。実際には、それは非常に迅速に発生しますが、これは理想的ではありません。

もし私が自分のやり方を持っていたら、java.lang.ThreadLocalに別のメソッドがあったでしょう:

protected void release() throws Throwable {}

このメソッドがjava.lang.ThreadLocalに存在し、スレッドの終了/終了時にJVMによって呼び出された場合、それを自分でオーバーライドして、接続を閉じることができます(そして、償還者はZionに来たはずです)。

そのような方法がない場合、私は閉鎖を確認する別の方法を探しています。JVMガベージコレクションに依存しない方法。

4

9 に答える 9

14

あなたが敏感な性向であるならば、今目をそらしてください。

これがうまく拡張できるとは思いません。システム内のスレッド数が実質的に2倍になります。それが許容できるいくつかのユースケースがあるかもしれません。

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();
}

代わりに THAT を実行させます。

メソッドにすることもできます。例:

  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 全体をシャットダウンすることがわかっていて、その時点で接続を閉じたい場合は、「すべてのタスクが完了し、executor でシャットダウンが呼び出された後に」閉じることも行うスレッド ファクトリを設定できます。例:

  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 が接続が以前に開かれたかどうかを知ることができるかどうかに関して、頭に浮かぶことの 1 つは、接続が開かれたかどうかを追跡するだけの 2 番目の 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フレームワークは、オブジェクトのライフサイクルとスレッドスコープの変数をサポートしているため(すべてうまくプロキシされています)。そのうちの1つを使用できますか?Springはこれをすべて箱から出して行うと思いますが、Guiceはライフサイクルとスレッドスコープを処理するためにサードパーティのlibを必要とします。

次に、ThreadLocal変数の作成またはスレッドの作成のいずれかをどの程度制御できますか?ThreadLocalを完全に制御できると思いますが、スレッドの作成は制限されていませんか?

アスペクト指向プログラミングを使用して、run()メソッドを拡張してクリーンアップを含める新しいRunnableまたはスレッドを監視できますか?また、ThreadLocalを拡張して、それ自体を登録できるようにする必要があります。

于 2009-11-10T11:03:57.127 に答える
1

サブクラスに List プロパティを設定するように、ThreaedLocal の get() メソッドをオーバーライドします。このプロパティを簡単に照会して、特定のスレッドに対して get() メソッドが呼び出されたかどうかを判断できます。その場合、ThreadLocal にアクセスしてクリーンアップできます。

コメントに応じて更新

于 2009-11-10T10:59:49.947 に答える
1

一般的なケースでは、クラシック以外にこれに対する適切な解決策はないと思います。リソースを取得するコードは、それを閉じる責任を負う必要があります。

特定のケースでは、この程度までスレッドを呼び出している場合、スレッドの開始時にメソッドへの接続を渡すことができます。これには、カスタム パラメーターを受け取るメソッドを使用するか、何らかの形式の依存性注入を使用します。次に、接続を提供するコードがあるので、それを削除するコードがあります。

接続を必要としないコードは接続を取得しないため、閉じる必要がないため、注釈に基づく依存性注入がここで機能する可能性がありますが、そのようなものを改造するには設計が行き過ぎているようです。

于 2009-11-09T16:38:54.353 に答える
1

接続を一度開く必要があったため、同じ場所で閉鎖も処理する必要があります。環境によっては、スレッドが再利用される場合があり、アプリケーションのシャットダウン前にスレッドがガベージ コレクションされることは期待できません。

于 2009-11-09T10:41:10.253 に答える