ホームページ  >  記事  >  Java  >  JavaにおけるThreadLocalの使用法と原理は何ですか

JavaにおけるThreadLocalの使用法と原理は何ですか

王林
王林転載
2023-04-13 17:31:121076ブラウズ

使用法

  • スレッド間でデータを分離する

  • スレッド内のすべてのメソッド、スレッド内のすべてのメソッドにパラメータを渡すことを回避します。 ThreadLocalで管理されているオブジェクトを取得します。

package com.example.test1.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class AsyncTest {

    // 使用threadlocal管理
    private static final ThreadLocal<SimpleDateFormat> dateFormatLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    // 不用threadlocal进行管理,用于对比
    SimpleDateFormat dateFormat = new SimpleDateFormat();

    // 线程名称以task开头
    @Async("taskExecutor")
    public void formatDateSync(String format, Date date) throws InterruptedException {
        SimpleDateFormat simpleDateFormat = dateFormatLocal.get();
        simpleDateFormat.applyPattern(format);
        
        // 所有方法都可以直接使用这个变量,而不用根据形参传入
        doSomething();
        
        Thread.sleep(1000);
        System.out.println("sync " + Thread.currentThread().getName() +  " | " + simpleDateFormat.format(date));
        
        // 线程执行完毕,清除数据
        dateFormatLocal.remove();
    }

    // 线程名称以task2开头
    @Async("taskExecutor2")
    public void formatDate(String format, Date date) throws InterruptedException {
        dateFormat.applyPattern(format);
        Thread.sleep(1000);
        System.out.println("normal " + Thread.currentThread().getName() +  " | " + dateFormat.format(date));
    }
}

junit を使用してテストします:

@Test
void test2() throws InterruptedException {
for(int index = 1; index <= 10; ++index){
String format = index + "-yyyy-MM-dd";
Date time = new Date();
asyncTest.formatDate(format, time);
}

for(int index = 1; index <= 10; ++index){
String format = index + "-yyyy-MM-dd";
Date time = new Date();
asyncTest.formatDateSync(format, time);
}
}

結果は次のとおりです。 によって管理されていない変数があることがわかります。 ThreadLocal は正しい形式と一致しませんでした。

同期タスク--10 | 10-2023-04-11
同期タスク--9 | 9-2023-04-11
通常タスク2-3 | 2-2023- 04-11
通常のタスク2-5 | 2-2023-04-11
通常のタスク2-10 | 2-2023-04-11
通常のタスク2-6 | 2-2023-04-11
同期タスク--1 | 1-2023-04-11
通常タスク2-7 | 2-2023-04-11
通常タスク2-8 | 2-2023-04-11
通常タスク2- 9 | 2-2023-04-11
同期タスク--6 | 6-2023-04-11
同期タスク--3 | 3-2023-04-11
同期タスク--2 | 2-2023-04-11
同期タスク--7 | 7-2023-04-11
同期タスク--4 | 4-2023-04-11
同期タスク--8 | 8- 2023-04-11
通常タスク2-4 | 2-2023-04-11
通常タスク2-1 | 2-2023-04-11
同期タスク--5 | 5-2023-04- 11
通常タスク2-2 | 2-2023-04-11

実装原則

ThreadLocal:

からデータを取得するプロセス

まず対応するスレッドを取得します。

getMap(t) を通じてスレッド内の ThreadLocalMap を取得します

#ThreadLocalMap

は再実装されたハッシュ テーブルです。 2 つの要素に基づくハッシュ:

    ユーザー定義の
  • ThreadLocal

    オブジェクト (例: dateFormatLocal)。

  • Entry

    value をカプセル化するオブジェクト。

map.getEntry(this)

メソッドを通じて、現在の threadlocal## に基づいてハッシュ テーブル内の対応する Entry# を取得します。 # object #get() を初めて使用する場合は、

setInitialValue()

を使用して、ユーザーがオーバーライドした initialValue() を呼び出します。 メソッド マップを作成し、ユーザー指定の値で初期化します。 この設計では、スレッドが終了すると、スレッド共有変数 ThreadLocalMap が破棄されます。

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

Entry オブジェクトは弱参照です:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    // k: ThreadLocal, v: value
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

弱参照の一般的な使用法は次のとおりです: <pre class="brush:java;">WeakReference&lt;RoleDTO&gt; weakReference = new WeakReference&lt;&gt;(new RoleDTO());</pre> したがって、

では、 Entry

,

k

は、弱参照である ThreadLocal オブジェクトを表します。 v は、ThreadLocal によって管理される value を表し、これは強参照です。 メモリ リーク

メモリ リーク

とは、役に立たないオブジェクト (もう使用されなくなったオブジェクト) がメモリを占有し続けるか、役に立たないオブジェクトのメモリが時間内に解放されず、その結果、メモリ リーク スペースの浪費はメモリ リークと呼ばれます。ガベージ コレクターのアクティビティが増加し、メモリ使用量が増加し続けると、プログラムのパフォーマンスが徐々に低下し、極端な場合には、

OutOfMemoryError

がトリガーされ、プログラムがクラッシュします。 メモリ リークの問題は主にスレッド プールで発生します。これは、スレッド プール内のスレッドが継続的に実行され、実行のためにタスク キューから新しいタスクが継続的に取得されるためです。ただし、タスクには ThreadLocal オブジェクトが存在する可能性があり、これらのオブジェクトの

ThreadLocal

はスレッドの ThreadLocalMap に保存されるため、ThreadLocalMapどんどん大きくなっていきます。 ただし、ThreadLocal はタスク (ワーカー) によって渡され、タスクの実行後、対応する

ThreadLocal

オブジェクトは破棄されます。スレッド内の関係は、Thread -> ThreadLoalMap -> Entry8cea09e86e6da166e71a296f2b1f24bd です。 ThreadLocalこれは弱い参照であるため、GC 中に破棄され、ThreadLoalMapEntry5e33282b25ec20a016ad69d03248472f が存在します。 remove()を使用します

スレッド プール内のスレッドは常に実行されているため、ThreadLoalMap がクリーンアップされていない場合は、

Entry2ce275f7b95bd469e229891ac13142eb

は常にメモリを占有します。 remove() メソッドは、key==nullEntry をクリアします。 静的変更を使用します

スレッド クラスをスレッド プールに複数回渡さないようにするには、ThreadLocal

static

に設定します。繰り返します。 エントリを作成します。たとえば、スレッド プールを使用して 10 個のタスクを処理するユーザー定義スレッド <pre class="brush:java;">public class Test implements Runnable{ private static ThreadLocal&lt;Integer&gt; local = new ThreadLocal&lt;&gt;(); @Override public void run() { // do something } }</pre> があります。次に、Entry70cecb753554e47d1300fe45bef564fc は、

static が追加されたため、スレッド プール内のタスクの処理に使用される各スレッドの

Thread.ThreadLocalMap に保存されます。 キーワード、各スレッドの Entry 内のすべての local 変数は同じ変数を参照します。この時点でメモリ リークが発生したとしても、すべての Test クラスには local オブジェクトが 1 つだけ存在するため、過度のメモリ使用が発生することはありません。 rree

以上がJavaにおけるThreadLocalの使用法と原理は何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。