blog

Javaは聞かなければならない: Threadローカル究極の章

開会の挨拶\n\n\n\n\n\n\n\n私が投げた?それは言語のどのような地獄ですか?これは、どのようなロジックああ、マルチスレッドに依頼し、そのような風邪を思い付くと言ったのですか?精神崩壊ああ、...

Sep 25, 2020 · 17 min. read
シェア

開会の挨拶

私が投げた?このTMは人間の言語ですか?ロジックのどのようなこれはああ、マルチスレッドを求めるために言ったし、そのような冷たいスレッドローカルに出てきたのですか? 精神崩壊ああ、その後、あなたはTM自身がそれを本を見てダウンして行くことがわからない忘れて、どのようなゴーストああ...への答えを見つけるために私に来る... ...

非常に不本意だったにもかかわらず、チャン・サンは小さな脳を高速で動かし、スレッドローカルの詳細をすべて思い出した...。

ThreadLocalの役割は、主にデータの分離を行うには、現在のスレッドにのみ属するデータで満たされ、変数のデータは、マルチスレッド環境では、他のスレッドから比較的分離され、どのように他のスレッドによって改ざんされるから、その変数を防ぐためです。

どのような場面で使用されるのでしょうか?

これは、私はめったに使用しないと述べたが、また、私に尋ねた、困難なああ、ああああ、覚えている、トランザクションの分離レベル。

Springは、スレッドローカルアプローチを使用すると、同時に、このアプローチを使用すると、ビジネス層は、トランザクションを使用することができます同じデータベース接続を使用してデータベース操作の単一のスレッドは、スイッチの設定、ハングアップとリカバリの間に複数のトランザクションの巧妙な管理、伝播レベルを通じて、接続オブジェクトを感知し、管理する必要はありません。

TransactionSynchronizationManagerSpring Frameworkはこの分離を実現するためにThreadLocalを使用しており、主にクラス内では以下のようなコードになっています。

private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
 private static final ThreadLocal<Map<Object, Object>> resources =
   new NamedThreadLocal<>("Transactional resources");
 private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
   new NamedThreadLocal<>("Transaction synchronizations");
 private static final ThreadLocal<String> currentTransactionName =
   new NamedThreadLocal<>("Current transaction name");
   

Springのトランザクションは、主にThreadLocalとAOPの実装を行うには、私はここで言及し、我々は、各スレッド独自のリンクが良いにThreadLocalによって保存されていることを知って、私はSpringの章の詳細にされる詳細を続行するには、暖かいですか?

ThreadLocalがソースコードで使われているシナリオ以外に、あなた自身がThreadLocalを使っているシナリオはありますか?普段はどのように使っていますか?

来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た、来た

on-lineは、ユーザーの日付のいくつかは、実際にはSimpleDataFormatポットをダウントラブルシューティングは、SimpleDataFormatのパース()メソッドを使用する場合、内部にはCalendarオブジェクトがあり、呼び出しSimpleDataFormatのパース()メソッドは、最初にCalendar.clear()を呼び出します。clear()、次にCalendar.add()を呼び出すと、スレッドが最初にadd()を呼び出した後、別のスレッドがclear()を呼び出した場合、この時間parse()メソッドの解析時間が正しくありません。

実際には、この問題を解決することは非常に簡単です、各スレッドは、それ自身のSimpleDataFormatを新規作成されますが、1000のスレッドは、1000のSimpleDataFormatを新規作成しないでください?

そこで当時は、スレッドプールとThreadLocalを使ってSimpleDataFormatをラップし、initialValueを呼び出して各スレッドにSimpleDataFormatのコピーを持たせることで、スレッドセーフの問題を解決し、パフォーマンスを向上させました。

その......

私は、オブジェクトに渡される必要があるメソッド呼び出しに遭遇することが多いスレッドがあるプロジェクトで、パラメータを渡すトランジションに問題があります。コンテキストは状態であり、多くの場合、ユーザーIDやタスク情報などです。

責任モデルの同様のチェーンに使用すると、各メソッドにコンテキストパラメータを追加するには、非常に面倒であり、コールチェーンは、ソースコードを変更することはできませんサードパーティのライブラリを持っている場合、いくつかの時間は、オブジェクトのパラメータを渡すことはできませんので、私は少し変換を行うためにThreadLocalを使用するように、呼び出しの前にThreadLocalでパラメータを設定する必要があり、他の場所が取得します。呼び出す前に ThreadLocal にパラメータを設定し、他の場所でパラメータを取得するだけです。

before
  
void work(User user) {
    getInfo(user);
    checkInfo(user);
    setSomeThing(user);
    log(user);
}
then
  
void work(User user) {
try{
   threadLocalUser.set(user);
   // 他们内部  User u = threadLocalUser.get();  
    getInfo();
    checkInfo();
    setSomeThing();
    log();
    } finally {
     threadLocalUser.remove();
    }
}

私は、クッキーやセッションなどのデータ分離がThreadLocalを経由して実装されている多くのシナリオを見てきました。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

うーん、よく答えてくれましたね。では、彼の根本的な実装の原理について少し教えてもらえますか?

ThreadLocal<String> localName = new ThreadLocal();
localName.set("張三");
String name = localName.get();
localName.remove();

実際には、本当に単純な使用は、スレッドが一般化することができますThreadLocalオブジェクトの初期化後に来て、このスレッド限り、取得するために削除した後、セットの値を取得することができます、ここで私は削除と述べたことに注意してください。

彼はスレッド間でデータの分離ができるので、他のスレッドがget()メソッドを使って他のスレッドの値を取得することはできません。

まず hisset のソースコードを見てください:

public void set(T value) {
    Thread t = Thread.currentThread();// 現在のスレッドを取得する
    ThreadLocalMap map = getMap(t);// スレッド "LocalMapオブジェクトを取得する
    if (map != null) // オブジェクトが空であるかどうかを確認する
        map.set(this, value); // 空のセットではない
    else
        createMap(t, value); // 空の地図オブジェクトを作成する
}
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
public class Thread implements Runnable {
       
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  
      

ThreadLocalデータ分離の真理は基本的にここにあります。各スレッドThreadはそれ自身のthreadLocals変数を保持するので、各スレッドがThreadLocalを作成するとき、データは実際に自分のスレッドThreadのthreadLocals変数に存在し、他の人がそれを取得する方法はありません。こうして分離が達成されます。

ThreadLocalMapの基本構造はどのようなものですか?

張さんは微笑んで、彼のデータ構造は、実際には非常にHashMapのようなマップがあるので、答えたが、ソースコードを見て見つけることができる、それはMapインターフェイスを実装していない、彼のエントリは、WeakReferenceを継承していますが、また、HashMapの次を見ていないので、チェーンテーブルはありません。

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
         
    }    

構造はおそらくこんな感じ:

ちょっと待って、2つ質問があるんですが、答えてもらえますか?

なぜ配列が必要なのですか?連鎖リストなしでハッシュの衝突を解決するには?

開発中、スレッドは異なるタイプのオブジェクトを格納するために複数の TreadLocal を持つことができますが、それらはすべて現在のスレッドの ThreadLocalMap に入れられるので、格納するために配列が絶対に必要です。

ハッシュのコンフリクトに関しては、まずソースコードを見てください:

private void set(ThreadLocal<?> key, Object value) {
           Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

現在の位置が空であれば、Entryオブジェクトが初期化され、i番目の位置に配置されます;

if (k == null) {
    replaceStaleEntry(key, value, i);
    return;
}

位置 i が NULL でない場合、Entry オブジェクトのキーが設定しようとしているキーであれば、Entry 内の値をリフレッシュします;

if (k == key) {
    e.value = value;
    return;
}

iの位置が空でなく、keyがentryと等しくなければ、空になるまで次の空の位置を探します。

この場合、取得では、また、スレッドローカルオブジェクトのハッシュ値に基づいてされ、テーブル内の位置を検索し、エントリオブジェクト内のキーの位置を決定し、キーを取得する同じですが、それは同じではない場合は、次の位置を判断し、競合が深刻である場合は、設定し、取得、効率はまだ非常に低いです。

これがgetのソースコードです。理解するのがとても気持ちよくないですか?

 private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
// get的时候一样是根据ThreadLocal获取到table的i值,然后查找数据拿到后会对比key是否相等  if (e != null && e.get() == key) 
            while (e != null) {
                ThreadLocal<?> k = e.get();
              // 直接返すために等しい、見つけるために継続するために等しくない、等しい位置を見つける。
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

オブジェクトはどこに保存されていますか?

Javaでは、スタック・メモリは1つのスレッドに帰属し、各スレッドはスタック・メモリを持ちます。スタック・メモリに格納された変数は、そのスレッドに属するスレッドにしか見えません。つまり、スタック・メモリはスレッドのプライベート・メモリと解釈できますが、ヒープ・メモリ内のオブジェクトはすべてのスレッドに見え、ヒープ・メモリ内のオブジェクトはすべてのスレッドからアクセスできます。

ということは、ThreadLocalのインスタンスとその値はスタックに保存されているということですか?

実際には、ThreadLocal インスタンスは、それが作成されたクラスによっても保持され、ThreadLocal の値もスレッドのインスタンスによって保持されます。

スレッドのThreadLocalデータを共有したい場合は?

InheritableThreadLocalInheritableThreadLocalInheritableThreadLocalを使用すると、メインスレッドで ThreadLocal のインスタンスを作成し、子スレッドでこのインスタンスによって設定された値を取得することによって、複数のスレッドが ThreadLocal の値にアクセスできるようになります。

private void test() {    
final ThreadLocal threadLocal = new InheritableThreadLocal();       
threadLocal.set("素晴らしい");    
Thread t = new Thread() {        
    @Override        
    public void run() {            
      super.run();            
      Log.i( "张三帅么 =" + threadLocal.get());        
    }    
  };          
  t.start(); 
} 

ああを渡すには?

パスのロジックは単純で、冒頭のスレッド・コードでthreadLocalsについて触れていますが、さらにその下に、意図的に別の変数を入れていることがわかります:

スレッドのソースコードで、Thread.initが最初に作成されたときに何をするのかを確認します:

public class Thread implements Runnable {
   
   if (inheritThreadLocals && parent.inheritableThreadLocals != null)
      this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
   
}

上の例のようにスレッドのinitThreadLocals変数がNULLでなく、親スレッドのinitThreadLocalsも存在する場合、親スレッドのinitThreadLocalsを現スレッドのinitThreadLocalsを現在のスレッドに渡します。

面白いでしょう?

ThreadLocalのディープなユーザーということですが、ThreadLocalの問題は見つかりましたか?

メモリリークのことですか?

どうしてこの子は、私が何を聞こうとしているのか知っているのかしら。そうそう、教えてよ。

この問題は確かに存在します。その理由をお話ししましょう。上記の私のコードを覚えていますか?

ThreadLocal は ThreadLocalMap に Key として自分自身を保存します。通常、key も value も外部から強く参照されるべきですが、現在は WeakReference によって弱く参照されるように設計されています。

まずは弱気の引用から:

弱い参照しか持たないオブジェクトのライフサイクルはずっと短く、ゴミ収集スレッドが管轄するメモリ領域をスキャンして弱い参照しか持たないオブジェクトを見つけると、現在のメモリ領域が十分かどうかに関係なく、すぐにそのメモリを取り戻します。

しかし、ゴミコレクターは優先順位の低いスレッドなので、弱い参照しか持たないオブジェクトをすぐに見つけられるとは限りません。

このため、外部からの強い参照がない場合、GCの際にThreadLocalが再取得されることになり、ThreadLocalを生成したスレッドが継続して実行されていた場合、このEntryオブジェクトの値がずっと再取得されず、メモリリークが発生する可能性があるという問題があります。

たとえば、スレッドプール内のスレッドは、スレッドが多重化され、その後、スレッドインスタンスが処理された後、多重化の目的のために、スレッドがまだ生きているので、ThreadLocalによって設定された値の値が保持され、メモリリークが発生します。

スレッドが使用された後の定義では、ThreadLocalMapは空にされるはずですが、今はスレッドが再利用されています。

その後、どのように解決するには?

コードの最後にremoveを使っても構いませんが、使用終了時にremoveで値をクリアすることを忘れないでください。

ThreadLocal<String> localName = new ThreadLocal();
try {
    localName.set("張三");
     
} finally {
    localName.remove();
}

removeのソースコードは非常にシンプルで、対応する値をすべてnullで検索し、ゴミ収集車がリサイクルするとき、自動的にそれらをリサイクルします。

では、なぜThreadLocalMapのキーは弱く参照されるように設計されているのでしょうか?

弱い参照に設定されていないキーは、エントリーの値と同じメモリリークのシナリオを引き起こします。

もう一点、ThreadLocalがないのは、nettyのfastThreadLocalを見れば改善できると思います。

まとめ

処理の設計の細部では、実際に多くの場合、神々の違いは、私はGoogleと自分の深い深い理解だけで、これは合理的であることを見つけるために、理不尽な点の多くは、本当に提供していないと思います。

ThreadLocalは、マルチスレッド、他のメソッドやクラスよりも使用頻度の内部の比較的冷たいクラスですが、私の記事を通じて、私はあなたが新しい認識を持っているかどうかわからない?

知れば知るほど知らないことが多いAoPです、ではまた次号で!

三聯の才能は蒼紅創作の最大の原動力であり、もしこのブログに誤りや提案があれば、メッセージを残す才能を歓迎します!

Read next

ES6学習ノート III: 拡張関数

3.拡張演算子 一般的な使用{}は、行に書くことができ、returnキーワード式を省略することができます{}を使用することができ、ログprint関数呼び出しで、戻り値はありませんし、未定義を表示します。

Sep 25, 2020 · 4 min read