記事ディレクトリ
スレッドローカルとは何ですか
ThreadLocal は、スレッド ローカル変数を提供する Java のツール クラスです。これは、スレッドごとに使用する変数コピーを作成でき、変数コピーにアクセスするときに各スレッドが互いに干渉することなく分離されることを意味します。
ThreadLocal の主な機能は次のとおりです。
- スレッドの分離: 各スレッドは変数の独自のコピーを持ち、相互に影響しません。
- パラメータの受け渡しを避ける: ThreadLocal を介してデータを渡します。メソッド間でパラメータを渡す必要はありません。
例を見てみましょう:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void add() {
threadLocal.set(threadLocal.get() + 1);
}
public static Integer get() {
return threadLocal.get();
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
add();
System.out.println(get());
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
add();
System.out.println(get());
}
}
});
thread1.start();
thread2.start();
}
}
出力結果:
1
2
3
1
2
3
各スレッドが threadLocal 変数の独立したコピーを持ち、それぞれが互いに影響を与えることなく独自の threadLocal 値を蓄積して出力することがわかります。これにより、スレッド分離の効果が得られます。
ThreadLocal を使用しない場合は、次のように共有変数を使用します。
public class NoThreadLocalExample {
private static int num;
public static void add() {
num++;
}
public static int get() {
return num;
}
public static void main(String[] args) {
// ...
}
}
2 つのスレッドが共有変数 num の変更と読み取りを競合するため、出力が狂います:
1
3
2
5
4
これは、ThreadLocal を使用しない場合に複数のスレッドが相互に影響を与える問題です。
ThreadLocal については、次の 3 つの主な注意点があります。
- メモリ リークのリスク: ThreadLocal は弱い参照を使用してデータを保存します。外部の強参照がない場合、スレッドの終了後にメモリ リークが発生する可能性があります。そのため、ThreadLocal を使用した後、remove() メソッドを呼び出してデータをクリアします。
- スレッド セーフ: ThreadLocal 自体はスレッド セーフですが、ThreadLocal に格納されているデータ オブジェクトは必ずしもスレッド セーフであるとは限りません。複数のスレッドが同じデータ オブジェクト上で動作する場合でも、スレッドの同期を考慮する必要があります。
- 継承: 子スレッドは、親スレッドによって ThreadLocal に設定されたデータを取得できません。各スレッドには変数の独自のコピーがあります。
概要:
ThreadLocal は、スレッドごとに独立した変数のコピーを提供し、スレッドの分離を実現し、パラメーターの受け渡しの影響を回避します。ただし、メモリ リークのリスクもあり、使用後にデータをクリアするには、remove() メソッドを呼び出す必要があります。また、ThreadLocal は単一スレッドの変数コピーのみを提供し、子スレッドは親スレッドのデータを取得できません。
ThreadLocal は、変数がスレッド間で分離され、メソッド呼び出しチェーンのコンテキストで渡されるシナリオに適しています。この外観により、同時実行性の高い環境でコードをよりシンプルかつエレガントに作成できるようになります。
ThreadLocal の内部実装原理は何ですか?
ThreadLocal は、内部クラス ThreadLocalMap を使用して、各スレッドのコピー変数を保存します。ThreadLocalMap はスレッド参照をキーとして使用し、変数のコピーは値としてハッシュ テーブルに保存されます。
ThreadLocal の get() メソッドが呼び出されると、最初に現在のスレッドが取得され、次にこのスレッドに関連付けられた ThreadLocalMap から変数のコピーが取得されます。set() メソッドも同様で、現在のスレッドの ThreadLocalMap の値を設定します。
ThreadLocal の実装ソース コードを見てください。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
次のことがわかります。
- Thread.currentThread() を通じて現在のスレッドを取得します。
- getMap() メソッドを通じてスレッドの ThreadLocalMap オブジェクトを取得します。
- ThreadLocalMap が存在する場合は、そこからスレッドローカル変数の値を取得または設定します
- ThreadLocalMap が存在しない場合は、値を設定する前に ThreadLocalMap オブジェクトを作成し、それをスレッドに関連付けます。
したがって、各スレッドは ThreadLocal をキーとした変数のコピーを格納する ThreadLocalMap 参照を保持し、各スレッドの変数分離を実現します。
また、ThreadLocal 自体は変数を識別するためのキーとしてのみ使用され、特定の値を保持しません。これがスレッド分離の実装の基礎でもあります。
ThreadLocal は、Java でスレッドローカル変数を実装するための重要なメカニズムです。各スレッドの変数のコピーが作成されるため、各スレッドは他のスレッドの対応するコピーに影響を与えることなく、独自のコピーを個別に変更できます。
ThreadLocal の実装原理は、内部クラス ThreadLocalMap を使用して各スレッドの変数コピーのマップを維持することです。ThreadLocalMap はスレッド参照をキーとして使用し、変数のコピーは値としてハッシュ テーブルに保存されます。
ThreadLocal を通じて変数コピーにアクセスまたは設定する場合、まず現在のスレッドの参照を取得し、次にこのスレッドを通じて関連する ThreadLocalMap 内の対応する値を取得または設定します。このようにして、スレッドごとに独立変数のコピーが作成され、スレッド分離の効果が得られます。
ThreadLocal のメイン API には、get()、set()、remove() の 3 つのメソッドしかありません。これはシンプルかつ強力であり、完全に理解すると、マルチスレッド プログラミングに大きな利便性がもたらされます。
ThreadLocal のアプリケーション シナリオには主に、データベース接続、トランザクション管理、ユーザー コンテキストの転送、パラメータ転送の回避などが含まれます。これは、変数がスレッド間で分離されており、メソッド呼び出しチェーンで渡す必要があるシナリオに適しています。
ただし、ThreadLocal を使用する場合は、次の 3 つの点にも注意する必要があります。
- メモリ リーク: ThreadLocal は弱参照を使用するため、外部強参照変数のコピーが存在しない場合、メモリ リークが発生する可能性があります。したがって、使用後は、remove() メソッドを呼び出して参照を削除します。
- スレッド セーフ: ThreadLocal に保存されるオブジェクトはスレッド セーフである必要があります。そうでないと、マルチスレッド環境で問題が発生します。
- 過剰使用: ThreadLocal を誤って使用すると、コードの理解と保守が困難になる可能性があります。
ThreadLocal を使用してトランザクション管理を実装します。
- 接続を保存する ThreadLocal 変数を定義します。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
- トランザクションの開始時にデータ ソースから接続を取得し、それを ThreadLocal に保存します。
public void beginTransaction() throws SQLException {
Connection conn = DataSourceUtils.getConnection();
connectionHolder.set(conn);
conn.setAutoCommit(false);
}
- トランザクションで get() メソッドを使用して、ThreadLocal から接続を取得します。
public void doSomeOperation() {
Connection conn = connectionHolder.get();
// 使用 conn 执行 SQL 操作
}
- トランザクションをコミットまたはロールバックするときは、ThreadLocal から接続を取得し、コミットまたはロールバックします。
public void commitTransaction() throws SQLException {
Connection conn = connectionHolder.get();
conn.commit();
conn.close();
connectionHolder.remove();
}
public void rollbackTransaction() throws SQLException {
Connection conn = connectionHolder.get();
conn.rollback();
conn.close();
connectionHolder.remove();
}
- トランザクションの後にremove()を呼び出して、ThreadLocalから接続を削除します。
このように、ThreadLocal を介して Connection にアクセスすると、トランザクションとスレッドのバインドが実現され、各スレッドがトランザクション管理用に独自の独立した Connection を持つことが保証されます。
これは、ThreadLocal を使用してシンプルなトランザクション管理を実装するというアイデアです。より堅牢なトランザクション管理フレームワークには、より多くのコンテンツが含まれますが、依然として ThreadLocal がスレッド分離の鍵となります。