ThreadLocalを深く理解する(説明がはっきりしている)

11803 ワード

ThreadLocalを徹底的に理解する
参照先:http://www.iteye.com/topic/103804 http://www.iteye.com/topic/777716
ソース分析
ThreadLocalクラスの動作原理を説明するためには、その動作が非常に密接な他のいくつかのクラスを同時に紹介しなければならない.
  • ThreadLocalMap(内部クラス)
  • Thread

  • 深入理解ThreadLocal(讲解清晰)_第1张图片
    まず、Threadクラスには次の行があります.
        /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;

    ここでThreadLocalMapクラスの定義はThreadLocalクラスであり,真の参照はThreadクラスである.同時に、ThreadLocalMapでデータを格納するentry定義:
            static class Entry extends WeakReference> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    このMapのkeyはThreadLocalクラスのインスタンスオブジェクトであり、valueはユーザーの値であり、ネット上のほとんどの例keyがスレッドの名前や識別ではないことがわかります.ThreadLocalのsetとgetメソッドコード:
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
    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();
    }
    getMapメソッド:
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }

    現在のThreadクラスオブジェクトのThreadlocalMapプロパティを初期化します.
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }

    ここまで来れば、ThreadLocalがどのように働いているのか理解できます.
  • Threadクラスには、ThreadLocalMapクラス(ThreadLocalクラスに定義された内部クラス)に属するメンバー変数があります.これはMapであり、keyはThreadLocalインスタンスオブジェクトです.
  • ThreadLocalクラスのオブジェクトset値である場合、まず現在のスレッドのThreadLocalMapクラス属性を取得し、次にThreadLocalクラスのオブジェクトをkeyとしてvalueを設定する.get値の場合は似ています.
  • ThreadLocal変数のアクティブ範囲は、あるスレッドであり、そのスレッドが「独自で、独自に占領されている」ものであり、その変数に対するすべての操作は、そのスレッドによって完了する!つまり、ThreadLocalは共有オブジェクトのマルチスレッドアクセスの競合問題を解決するためのものではない.set()スレッド内のオブジェクトは、そのスレッド自体が使用するオブジェクトであり、他のスレッドはアクセスする必要はなく、アクセスもできません.スレッドが終了すると、これらの値はゴミとして回収されます.
  • はThreadLocalの動作原理によって決定される:各スレッドは独自に変数を持ち、共有されていない.以下に例を示す:
  • public class Son implements Cloneable{
        public static void main(String[] args){
            Son p=new Son();
            System.out.println(p);
            Thread t = new Thread(new Runnable(){  
                public void run(){
                    ThreadLocal threadLocal = new ThreadLocal<>();
                    System.out.println(threadLocal);
                    threadLocal.set(p);
                    System.out.println(threadLocal.get());
                    threadLocal.remove();
                    try {
                        threadLocal.set((Son) p.clone());
                        System.out.println(threadLocal.get());
                    } catch (CloneNotSupportedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(threadLocal);
                }}); 
            t.start();
        }
    }

    出力:
    Son@7852e922
    java.lang.ThreadLocal@3ffc8195
    Son@7852e922
    Son@313b781a
    java.lang.ThreadLocal@3ffc8195

    つまり、共有するオブジェクトを直接ThreadLocalに保存すると、複数のスレッドのThreadLocalとなる.get()が取得したのはやはりこの共有オブジェクト自体なのか,それとも同時アクセスの問題があるのか.したがって、ThreadLocalに保存する前に、クローンまたはnewで新しいオブジェクトを作成してから保存します.ThreadLocalの役割は、スレッド内のローカル変数を提供することであり、この変数はスレッドのライフサイクル内で機能します.役割:1つのスレッド内の共通変数(今回要求されたユーザー情報など)を提供し、同じスレッド内の複数の関数またはコンポーネント間の共通変数の伝達の複雑さを低減したり、スレッドにプライベートな変数のコピーを提供したりすることで、各スレッドは他のスレッドに影響を与えることなく、自分の変数のコピーを任意に変更することができます.
    スレッドの複数のThreadLocalオブジェクトをどのように実装し、各ThreadLocalオブジェクトをどのように区別しますか?ソースコードを表示すると、次のように表示されます.
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
          return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    各ThreadLocalオブジェクトにはfinal修飾int型のthreadLocalHashCode可変属性があり、基本データ型については初期化後は変更できないと考えられるため、ThreadLocalオブジェクトを一意に特定することができる.しかし、同時にインスタンス化された2つのThreadLocalオブジェクトに異なるthreadLocalHashCode属性があることをどのように保証するか:ThreadLocalクラスにはstatic修飾のAtomicInteger([əˈtɒmɪk]原子操作を提供するIntegerクラスのメンバー変数(すなわちクラス変数)とstatic final修飾の定数(2つの隣接するnextHashCodeの差として).nextHashCodeはクラス変数であるため、各呼び出しThreadLocalクラスはnextHashCodeが新しい値に更新されることを保証し、次の呼び出しThreadLocalクラスという更新された値は依然として利用可能であり、AtomicIntegerはnextHashCodeの自己増加の原子性を保証する.
    なぜスレッドidを直接ThreadLocalMapのkeyとして使用しないのですか?この点は分かりやすいが,スレッドidを直接ThreadLocalMapのkeyとして用いているため,ThreadLocalMapに入れる複数のvalueを区別できない.例えば、私たちは2つの文字列を入れましたが、どの文字列を取り出すかどうやって知っていますか?一方、キーとしてThreadLocalを使用するのは異なり、各ThreadLocalオブジェクトはthreadLocalHashCode属性によって一意に区別できるか、あるいは各ThreadLocalオブジェクトはこのオブジェクトの名前によって一意に区別できる(以下の例)ため、異なるThreadLocalをキーとして、異なるvalueを区別し、アクセスしやすい.
    public class Son implements Cloneable{
        public static void main(String[] args){
            Thread t = new Thread(new Runnable(){  
                public void run(){
                    ThreadLocal threadLocal1 = new ThreadLocal<>();
                    threadLocal1.set(new Son());
                    System.out.println(threadLocal1.get());
                    ThreadLocal threadLocal2 = new ThreadLocal<>();
                    threadLocal2.set(new Son());
                    System.out.println(threadLocal2.get());
                }}); 
            t.start();
        }
    }

    ThreadLocalのメモリリークの問題は、上記のEntryメソッドのソースコードに基づいて、ThreadLocalMapがThreadLocalの弱い参照をKeyとして使用していることを知っています.次の図は、本明細書で説明したオブジェクト間の参照関係図です.実線は強参照、破線は弱参照です.
    以上の図のように、ThreadLocalMapはThreadLocalの弱い参照をkeyとして使用し、1つのThreadLocalが外部から強く引用されていない場合、システムgcの場合、このThreadLocalは必ず回収され、これにより、ThreadLocalMapにkeyがnullのEntryが現れ、これらのkeyがnullのEntryのvalueにアクセスできなくなります.現在のスレッドが遅々として終了しない場合、これらのkeyがnullのEntryのvalueには、Thread Ref->Thread->Thread->ThreaLocalMap->Entry->valueが回収できず、メモリが漏洩します.ThreadLocalMapの設計時の上記の問題に対する対策:ThreadLocalMapのgetEntry関数の流れは大体:
  • は、まず、ThreadLocalの直接インデックス位置(ThreadLocal.threadLocalHashCode&(table.length-1)演算により得られる)からEntry eを取得し、eがnullでなくkeyが同じであればeを返す.
  • eがnullまたはkeyが一致しない場合は次の場所へクエリーし、次の場所のkeyが現在クエリーが必要なkeyと等しい場合は、対応するEntryを返します.それ以外の場合、key値がnullの場合、その位置のEntryを消去し、次の位置へのクエリーを続行します.この過程で出会ったkeyがnullのEntryが消去されると、Entry内のvalueにも強い参照チェーンがなく、自然に回収されます.コードをよく研究すると、set操作にも似たような考えがあり、keyがnullのEntryを削除し、メモリの漏洩を防ぐことができます.しかし、それだけでは十分ではありません.上の設計構想は、ThreadLocalMapのgetEntry関数またはset関数を呼び出すという前提条件に依存しています.これはもちろん何も成り立たないので、使用者がThreadLocalのremove関数を手動で呼び出し、不要になったThreadLocalを手動で削除してメモリの漏洩を防ぐ必要がある場合が多い.だからJDKはThreadLocal変数をprivate staticと定義することを提案して、このようにすればThreadLocalのライフサイクルはもっと長くて、ずっとThreadLocalの強い引用が存在するため、ThreadLocalも回収されないで、いつでもThreadLocalの弱い引用によってEntryのvalue値にアクセスすることができることを保証して、それからremoveそれを使って、メモリの漏洩を防ぐことができます.

  • ThreadLocalMap内部クラスについては初期容量16,負荷因子2/3を簡単に紹介し,衝突を解決する方法は再hash法,すなわち,現在のhashに基づいて定数をさらに自増する.