【Java同時プログラミング】ThreadLocalを深く分析する(八)


クラスを使用するときは、まず何ができるかを知ってから、その動作原理を深く分析します.ThreadLocalは名前から見ると「ローカルスレッド」のように見えます"つまり、ThreadLocalはスレッドではなく、スレッドのローカル変数である.ThreadLocalを使用して変数を維持する場合、ThreadLocalはその変数を使用するスレッドごとに独立した変数コピーを提供するので、各スレッドは他のスレッドに対応するコピーに影響を与えることなく、独立して自分のコピーを変更することができる.Javaマルチスレッド面接問題では、著者らはThrに対してeadLocalの答えは:  ThreadLocalはJavaの特殊な変数です.スレッドごとにThreadLocalがあります.スレッドごとに独自の変数があり、競争条件が完全に解消されています.それはコストの高いオブジェクトを作成するためにスレッドのセキュリティを取得する良い方法です.たとえば、ThreadLocalを使用してSimpleDateFormatをスレッドのセキュリティにすることができます.そのクラスは作成コストが高いためです.呼び出しのたびに異なるインスタンスを作成する必要があるため、ローカル範囲で使用する価値はありません.各スレッドに独自の変数コピーを提供すると、効率が大幅に向上します.まず、多重化によってコストの高いオブジェクトの作成個数が減少します.次に、高コストの同期または不変性を使用せずにスレッドのセキュリティが得られます.スレッドローカル部変数のもう1つの良い例は、マルチスレッド環境でコストの高いRandomオブジェクトを作成する個数を減らすThreadLocalRandomクラスです.
公式のThreadLocalの説明:
    このクラスには、スレッドローカル(thread-local)変数が用意されています.これらの変数は、変数(getメソッドまたはsetメソッドによって)にアクセスする各スレッドに独自のローカル変数があり、変数の初期化コピーとは独立しています.ThreadLocalの例は、通常、クラス内のprivate staticフィールドであり、ステータスをスレッドに依存させたいためです.(例えば、ユーザIDまたはトランザクションID)が関連付けられている.
一、ThreadLocalソース分析
   1.1属性
/**
 * threadLocalHashCode    Entry      
 */
private final int threadLocalHashCode = nextHashCode();

/**
 *      ,    threadLocalHashCode     
 * .
 */
private static AtomicInteger nextHashCode = new AtomicInteger();

/**
 *           ThreadLocal   threadLocalHashCode    
 */
private static final int HASH_INCREMENT = 0x61c88647;

   1.2内部クラスThreadLocalMap
  ThreadLocalMapの実現原理はHashMapとあまり差がなく、内部にはEntry配列があり、1つのEntryは通常少なくともKey,Valueを含み、検索時にkeyのHash値と配列長で計算することでEntryの配列中の位置を得、対応するvalueを得る.しかし、より特殊なのはEntryがソフトリファレンスWeakReferenceを継承し、つまりEntryは次のゴミまでしか生存できないごみを回収する前に、実際にはvalueではなくkeyを参照しています. 
 /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

   1.2.1 なぜThreadLocalMapでThreadLocalオブジェクトを弱引用するのか.
  弱いリファレンスを使用するメリットは、メモリの使用を減らすことです.set()、put()の待機操作には、このコードThreadLocal k=e.get()が表示されます.通常、弱いリファレンスの使用では、GCで回収されたかどうかを判断します.ThreadLocalMapがEntryのkey(ThreadLocalオブジェクト)を知っている場合すでに回収されているので、対応する値も役に立たない.その後cleanSomeSlots()を呼び出して関連する値をクリアし、ThreadLocalMapが常にできるだけ小さく維持されることを保証する. 
   1.2.2 cleanSomeSlots()操作
 private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }
 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

   1.3 set()操作
  ハッシュハッシュのキー値データが格納中に衝突する可能性があります.HashMapに格納されているのはEntryチェーンであることがわかります.ハッシュが衝突した後、新しいEntryをチェーンテーブルの先端に格納します.ただし、ThreadLocalMapとは異なり、index+1をハッシュのhash値として書き込みます.また、キーにnullが表示されたのは、Entryのキーがソフトインデックスを継承しているためですでは、次のGCで参照があるかどうかにかかわらず回収され、Valueは回収されません.nullが発生すると、replaceStaleEntry()メソッドが呼び出され、同じキーが存在する場合は古い値が直接置き換えられます.存在しない場合は、現在のビットに新しいEntryが再作成されます.
//               。
private void set(ThreadLocal key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    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;
        }
        // HashMap   ,  Entry key      ,   k null   !      replaceStaleEntry         key
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    //  cleanSomeSlots() table    ,      Entry   ,    size     ,    rehash()  
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


   1.4 get()操作
  get()は、現在のスレッドに対応するスレッドのローカル変数を取得します.ThreadLocalもHashMapと同様に、table配列におけるEntryの位置をhashで計算し、keyを使用して対応するvalueを取得します.valueがnullの場合、setInitialValue戻り値が呼び出されます.
 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
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;

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

  例を挙げて説明しないで、ネット上でいくつかThreadLocalの例と応用シーンについて探して、みんなは参考にすることができます.http://www.cnblogs.com/dolphin0520/p/3920407.htmlhttp://blog.csdn.net/JaCman/article/details/50458801
作者:小さなロバ、ゲームマン
夢:世界平和
原文住所:http://blog.csdn.net/liulongling
間違いがあれば、ご理解と批判の指摘を歓迎してください.
   
このブログには転載した文章が著者のロバの所有であることが明記されていないので、転載を歓迎しますが、著者の同意を得ずにこの声明を保留し、文章のページの明らかな位置で原文の接続を与えなければなりません.そうしないと、法律責任を追及する権利を保留します.