ここで示したThreadLocalの目的は、変数がThreadLocal変数を含むオブジェクトにアクセスするすべてのスレッドに対してローカルであることを示しています。ThreadLocal変数をクラスのメンバーとして持ち、それをスレッド自体に対してローカル変数にするのではなく、スレッドに対してローカルにするという点で、どのような違いがありますか?
6 に答える
スレッドは実行の単位であるため、複数のスレッドが同じコードを同時に実行できます。複数のスレッドがオブジェクト/インスタンスで同時に実行される場合、それらはインスタンス変数を共有します。各スレッドには独自のローカル変数がありますが、パラメーターを渡さずにオブジェクト間でこれらを共有することは困難です。
例として最もよく説明されています。ログインしたユーザーを取得してコードを実行するサーブレットがあるとします。
doGet(HttpServletRequest req, HttpServletResponse resp) {
User user = getLoggedInUser(req);
doSomething()
doSomethingElse()
renderResponse(resp)
}
doSomething()メソッドがユーザーオブジェクトにアクセスする必要がある場合はどうなりますか?各スレッドは同じユーザーオブジェクトを使用するため、ユーザーオブジェクトをインスタンスまたは静的変数にすることはできません。ユーザーオブジェクトをパラメーターとして渡すこともできますが、これはすぐに面倒になり、ユーザーオブジェクトをすべてのメソッド呼び出しにリークします。
doGet(HttpServletRequest req, HttpServletResponse resp) {
User user = getLoggedInUser(req);
doSomething(user)
doSomethingElse(user)
renderResponse(resp,user)
}
より洗練された解決策は、ユーザーオブジェクトをThreadLocalに配置することです。
doGet(HttpServletRequest req, HttpServletResponse resp) {
User user = getLoggedInUser(req);
StaticClass.getThreadLocal().set(user)
try {
doSomething()
doSomethingElse()
renderResponse(resp)
}
finally {
StaticClass.getThreadLocal().remove()
}
}
これで、いつでもユーザーオブジェクトを必要とするコードは、それらの厄介な追加パラメーターに頼る必要なしに、ローカルスレッドから抽出することでそれを取得できます。
User user = StaticClass.getThreadLocal().get()
このアプローチを使用する場合は、finallyブロックでオブジェクトを再度削除するように注意してください。そうしないと、スレッドプールを使用する環境(Tomcatアプリサーバーなど)でユーザーオブジェクトがハングアップする可能性があります。
編集:静的クラスのコード
class StaticClass {
static private ThreadLocal<User> threadLocal = new ThreadLocal<>();
static ThreadLocal<User> getThreadLocal() {
return threadLocal;
}
}
Threadを拡張するクラスのインスタンスは、実際のJavaスレッド(コードを実行して実行する「実行ポインター」として想像できます)と同じものではないことを理解する必要があります。
このようなクラスのインスタンスはJavaスレッドを表し、それを操作(割り込みなど)できますが、それを除けば、それらは単なる通常のオブジェクトであり、オブジェクトへの参照を取得できるすべてのスレッドからメンバーにアクセスできます(これは難しいことではありません)。
もちろん、メンバーをプライベートに保ち、メンバーrun()
から呼び出されたメソッドまたはメソッドによってのみ使用されるようにすることもできます(パブリックメソッドは他のスレッドからも呼び出すことができます)が、これはエラーが発生しやすく、実際には実行可能ではありません。すべてのデータをThreadサブクラスに保持したくない複雑なシステム(実際には、Threadをサブクラス化するのではなく、代わりにRunnableを使用することになっています)。
ThreadLocalは、多大な労力や設計上の妥協を必要とせずに、他のスレッドが同時にアクセスできないスレッドごとのデータを取得するためのシンプルで柔軟な方法です。
Threadオブジェクトは内部データメンバーを持つことができますが、これらはThreadオブジェクトへの参照を持っている(または取得できる)誰でもアクセスできます。ThreadLocalは、それにアクセスする各スレッドにのみ意図的に関連付けられています。利点は、(ThreadLocalのコンテキスト内で)同時実行の問題がないことです。スレッドの内部データメンバーには、共有状態と同じ同時実行性の問題があります。
結果を特定のスレッドに関連付けるという考え方を説明しましょう。ThreadLocalの本質は次のようなものです。
public class MyLocal<T> {
private final Map<Thread, T> values = new HashMap<Thread, T>();
public T get() {
return values.get(Thread.currentThread());
}
public void set(T t) {
values.put(Thread.currentThread(), t);
}
}
今ではそれ以上のものがありますが、ご覧のとおり、返される値は現在のスレッドによって決定されます。これが、各スレッドに対してローカルである理由です。
ThreadLocal is very useful in webapplications. The typical pattern is that somewhere at the start of the processing of a web request (usually in a servlet filter) state is stored in a ThreadLocal variable. Because all of the processing for a request is done in 1 thread all components participating in the request have access to this variable.
There is a wikipedia entry about this problem area. In our environment it's usually used to keep things local to request. On the server side a request is handled mostly by a single thread. To keep things local you put your data, e.g. the session data, in a thread local variable. This data is invisible to the other request (threads) and therefore you don't need to synchronize it with the other requests.
And donn't forget, there are JAVA API constructs, that are not thread safe, e.g. DateFormat. A static instance of DateFormat just doesn't work on the server side.
In fact, it's easier to handle multi threaded programming when you use your own private copy of data than handling with locks and monitors.
ThreadLocalsの利点は、プレーンなバニラスレッド...またはスレッドの任意のサブクラスで実行されるメソッドで使用できることです。
対照的に、スレッドローカルをThreadのカスタムサブクラスのメンバーとして実装する必要がある場合は、実行できないことがたくさんあります。たとえば、既存のバニラスレッドインスタンスでメソッドを実行する必要がある場合、アプリケーションは問題になります。つまり、アプリケーションの作成者が作成しておらず、変更できないライブラリコードによって作成されたインスタンス。