私のJavaクラスでは、主に不要なオブジェクトの作成を回避する手段として、ThreadLocal
を時々使用します:
_@net.jcip.annotations.ThreadSafe
public class DateSensitiveThing {
private final Date then;
public DateSensitiveThing(Date then) {
this.then = then;
}
private static final ThreadLocal<Calendar> threadCal = new ThreadLocal<Calendar>() {
@Override
protected Calendar initialValue() {
return new GregorianCalendar();
}
};
public Date doCalc(int n) {
Calendar c = threadCal.get();
c.setTime(this.then):
// use n to mutate c
return c.getTime();
}
}
_
私はこれを適切な理由で行います-GregorianCalendar
は、見事にステートフルで変更可能な、スレッドセーフではないオブジェクトの1つであり、値を表すのではなく、複数の呼び出しにわたってサービスを提供します。さらに、インスタンス化は「高価」であると見なされます(これが正しいかどうかは、この質問の要点ではありません)。 (全体的に、私は本当にそれを賞賛します:-))
ただし、スレッドをプールする任意の環境でこのようなクラスを使用すると、アプリケーションがそれらのスレッドのライフサイクルを制御していないandの場合、メモリリークが発生する可能性があります。サーブレット環境が良い例です。
実際、Tomcat 7はWebアプリケーションが停止するとそのように鳴ります。
重大:Webアプリケーション[]が、タイプ[org.Apache.xmlbeans.impl.store.CharUtil $ 1](値[org.Apache.xmlbeans.impl.store.CharUtil$1@2aace7a7])のキーと値を持つThreadLocalを作成しましたタイプ[Java.lang.ref.SoftReference](値[Java.lang.ref.SoftReference@3d9c9ad4])ですが、Webアプリケーションが停止しているときに削除できませんでした。スレッドは、可能性のあるメモリリークを回避するために、時間の経過とともに更新されます。 2012年12月13日12:54:30 PM org.Apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
(その特定のケースでは、私のコードでさえそうしていません)。
これは公平に思えません。 Tomcatは、正しいことを行ったとしてme(または私のクラスのユーザー)を非難しています。
結局のところ、Tomcatはother Webアプリのために、私に提供したスレッドを再利用したいと考えているからです。 (うーん、汚い気がします。)おそらく、Tomcatの側では、それは素晴らしいポリシーではありません-スレッドは実際に状態を持っている/原因があるので、アプリケーション間で共有しないでください。
ただし、このポリシーは望ましくない場合でも、少なくとも一般的です。 ThreadLocal
ユーザーとして、自分のクラスがさまざまなスレッドにアタッチしたリソースを「解放」する方法を提供する義務があると感じています。
ここで正しいことは何ですか?
私には、サーブレットエンジンのスレッド再利用ポリシーがThreadLocal
の背後にある意図と矛盾しているようです。
しかし、おそらく、ユーザーが「このクラスに関連付けられた、悪質なスレッド固有の状態になりましたが、スレッドを終了させてGCに任せる立場にないのですが」とユーザーに言わせる機能を提供する必要があります。私がこれを行うことさえ可能ですか?つまり、過去にThreadLocal#remove()
を見た各スレッドでThreadLocal#initialValue()
が呼び出されるようにすることはできません。それとも別の方法がありますか?
それとも、ユーザーに「適切なクラスローダーとスレッドプールの実装を取得する」とだけ言うべきでしょうか。
EDIT#1:スレッドのライフサイクルを認識しないバナイラユーティリティクラスでのthreadCal
の使用方法を明確化EDIT#2:DateSensitiveThing
のスレッドセーフティの問題を修正
さて、これでパーティーに少し遅れました。 2007年10月、Josh Bloch(Doug Leaと共に_Java.lang.ThreadLocal
_の共著者) 書き込み :
「スレッドプールの使用には細心の注意が必要です。スレッドローカルのずさんな使用と組み合わせたスレッドプールのずさんな使用は、多くの場所で指摘されているように、意図しないオブジェクト保持を引き起こす可能性があります。」
それでも、人々はThreadLocalとスレッドプールの不適切な相互作用について不満を言っていました。しかし、ジョシュは制裁を行いました:
「パフォーマンスのためのスレッドごとのインスタンス。AaronのSimpleDateFormatの例(上記)は、このパターンの一例です。」
ThreadLocal
を使用して「プール」する場合、そのためのオプションは限られています。いずれか:a)あなたが知っている値を入れるThread
(s)は、アプリケーションが終了すると終了することを知っています。 OR b)後で調整して、ThreadLocal#set()を呼び出した同じスレッドを呼び出して、ThreadLocal#remove( )アプリケーションが終了したときつまり、「ローカルスレッドインスタンスプール」への高速で競合のないアクセスの形式としてThreadLocalを使用することを決定することは、軽視すべき決定ではありません。
注:「オブジェクトプール」以外のThreadLocalの使用法は他にもあり、これらのレッスンは、ThreadLocalが一時的にのみ設定されることを意図している場合や、維持するスレッドごとの状態が本物である場合には適用されません。トラックの。
ライブラリの実装者にとっては、いくつかの影響があります(そのようなライブラリがプロジェクトの単純なユーティリティクラスである場合でも)。
どちらか:
Java.util.concurrent.ThreadLocalRandom
_を実装している場合は、適切な場合があります。 (_Java.*
_で実装していない場合、Tomcatは引き続きライブラリのユーザーに怒鳴る可能性があります)。 _Java.*
_がThreadLocalテクニックを節約するための規律に注目するのは興味深いことです。OR
LibClass.releaseThreadLocalsForThread()
の呼び出しに使用したすべてのスレッドを、それらが終了したときに調整できます。ただし、ライブラリを「適切に使用するのが難しい」ようにします。
OR
new ExpensiveObjectFactory<T>() { public T get() {...} }
を提供できます。」.そんなに悪くない。オブジェクトが本当に重要で作成に費用がかかる場合、明示的なプーリングはおそらく価値があります。
OR
servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})
将来のJavaEE仕様で、最後の3つの項目に関するいくつかの標準化が見られるといいですね。
実際、GregorianCalendar
のインスタンス化はかなり軽量です。ほとんどの作業が発生するのはsetTime()
への避けられない呼び出しです。また、スレッドの実行の異なるポイント間で重要な状態を保持しません。 Calendar
をThreadLocal
に入れても、プロファイリングでnew GregorianCalendar()
にホットスポットが確実に表示されない限り、コストよりも多くの効果が得られる可能性はほとんどありません。
new SimpleDateFormat(String)
は、フォーマット文字列を解析する必要があるため、比較するとコストがかかります。解析されると、オブジェクトの「状態」は、後で同じスレッドが使用するときに重要になります。それはより良い適合です。ただし、クラスに追加の責任を与えるよりも、新しいインスタンスをインスタンス化する方が「費用がかからない」場合があります。
スレッドはあなたが作成したものではなく、あなたが借りただけです。使用する前に、きれいにしておかなければならないのは当然のことだと思います。 Tomcatはそれ自体ですべてをクリーンアップできますが、忘れ物を思い出させてくれます。
追加:準備されたGregorianCalendarの使用方法は単純に間違っています。サービスリクエストは同時に実行でき、同期がないため、doCalc
は別のリクエストによってgetTime
ater setTime
を呼び出すことができます。同期を導入すると処理が遅くなるため、新しいGregorianCalendar
を作成する方が良いオプションになる可能性があります。
言い換えると、質問は次のとおりです。準備されたGregorianCalendar
インスタンスのプールを保持して、その数が要求レートに合わせて調整されるようにする方法。したがって、少なくとも、そのプールを含むシングルトンが必要です。各Iocコンテナーには、シングルトンを管理する手段があり、ほとんどのオブジェクトには実装可能なオブジェクトプールがあります。 IoCコンテナをまだ使用していない場合は、ホイールを再発明するのではなく、IoCコンテナ(String、Guice)を使い始めます。
その助けがあれば、カスタムSPI(インターフェース)とJDK ServiceLoader
を使用します。次に、スレッドローカルのアンロードを実行する必要があるすべてのさまざまな内部ライブラリ(jar)をServiceLoaderパターンに従うので、jarがスレッドローカルクリーンアップを必要とする場合、適切な/META-INF/services/interface.name
があれば、jarは自動的に選択されます。
次に、フィルターまたはリスナーでアンロードを実行します(リスナーにいくつかの問題がありましたが、何を思い出すことができません)。
JDK/JEEにスレッドローカルをクリアするための標準 SPI=)が付属していると理想的です。
JDKのThreadPoolExecutorは、タスクの実行後にThreadLocalsのクリーニングを実行できたと思いますが、そうではありません。私はそれが少なくともオプションを提供できたと思います。スレッドがTreadLocalマップへのパッケージプライベートアクセスのみを提供するため、ThreadPoolExecutorは、スレッドのAPIを変更せずにそれらにアクセスできないためです。
興味深いことに、ThreadPoolExecutorはメソッドスタブbeforeExecution
およびafterExecution
を保護しています。APIによると:These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals...
。したがって、ThreadLocalCleanerインターフェースを実装するTaskと、afterExecutionでtaskのcleanThreadLocals()を呼び出すカスタムThreadPoolExecutorを想像できます。
このことを1年間考えた後、JavaEEコンテナがプールされたワーカースレッドを無関係なアプリケーションのインスタンス間で共有することは受け入れられないと判断しました。これは「エンタープライズ」ではありません。
実際にスレッドを共有する場合は、_Java.lang.Thread
_(少なくともJavaEE環境では)がsetContextState(int key)
やforgetContextState(int key)
(mirroring setClasLoaderContext()
)は、さまざまなアプリケーション間でスレッドを渡すときに、コンテナがアプリケーション固有のThreadLocal状態を分離できるようにします。
_Java.lang
_名前空間でのこのような変更は保留中ですが、アプリケーションデプロイヤが「1つのスレッドプール、1つの関連アプリケーションのインスタンス」ルールを採用すること、およびアプリケーション開発者が「このスレッドはThreadDeathまでマイニングすること」私たちは一部を行います。