マルチスレッドのThreadLocal類(一):ThreadLocal概要及びソースコード解析による運行原理

12459 ワード

前言:
本文はThreadLocal類の最初の文章として、主にソースコードを通してThreadLocal類の実現原理を分析し、その運行方式を要約し、どのようにThreadLocalを使うかを簡単に分析します。
1、ThreadLocal類の概要
1.1、何がThreadLocalですか?
ソースの第一歩を理解するには、優先的にクラスファイルのトップコメントを解読する必要があります。
/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * 

For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. *


 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal<Integer> threadId =
 *         new ThreadLocal<Integer>() {
 *             @Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * 
*Each thread holds an implicit reference to its copy of a thread-local
*variable as long as the thread is alive and the{@code ThreadLocal}
*instance is accessible;after a thread goes away、all of its copies of
*thread-local instances are subject to garbage collection(unless other)
*references to these copies exist)

*@author Josh Bloch and Dug Lea
*@since 1.2
*/
トップコメントからは、ThreadLocalが する は、 の とは なります。ThreadLocalはスレッドの を し、 スレッドはget()とset()によってこの を することができますが、 のスレッドの と することなくマルチスレッドの にスレッドのデータ を します。
に いますと、ThreadLocalに された は のスレッドに し、この は のスレッドから されています。
1.2、なぜThreadLocal を しますか?
の のいくつかのよくある を り ってみますと、 えばブラウザがウェブサイトにアクセスする 、 が したウェブサイトによってブラウザのローカルキャッシュから するCookieを します。そしてこのCookieを ヘッダに めて、AサイトのCookieを しに きます。そして、 のブラウザ のデータは いに えません。 えば、Googleブラウザキャッシュに されているCookieファイアフォックスブラウザは えません。もちろん、ファイアフォックスブラウザがAサイトを いて、GoogleブラウザでAサイトのCookieを つけて、 ヘッダに れて して、データ を することもできません。
この では、ブラウザは のような を たしています。 えば、GoogleのブラウザでAサイトを いたら、Googleのブラウザという「 」に って、「AサイトのCookieをください」と って、AサイトのCookieを に れることができます。
もう つの をあげます。 たちが する でよく われている の です。もし の は のネットショッピングの を の の に いたら、 が を りに く 、 する の を し てるのでさえすれば、 はコードをスキャンして、 で の は ら の の のあの の を けて、 が1つの の1つの を す はありません。もし の を えたら、 してから に します。
( を げて なところがあれば、 げてください)
マルチスレッドのプログラミングでは、ThreadLocalもこの を めており、マルチスレッドでThreadLocalのget()とset() を び すと、 のスレッドによって または する を り すことができ、 な をする がなく、スレッド データ を した でパラメータ を することができます。
2、ThreadLocalの
public class Main {

    public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal();
        for (int i=0; i<5; ++i) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        threadLocal.set(Thread.currentThread().getName());
                        System.out.println(Thread.currentThread().getName() + " is stored");
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getId() + ":" + threadLocal.get());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            }).start();
        }
    }

}
:
Thread-0 is stored
Thread-2 is stored
Thread-3 is stored
Thread-1 is stored
Thread-4 is stored
13:Thread-2
14:Thread-3
15:Thread-4
11:Thread-0
12:Thread-1

Process finished with exit code 0
3、ThreadLocalの
ThreadLocalの を るには、ソースコードを む があります。 はJDK 1.8を にして、ThreadLocalのソースコードを します。このうち、get()とset()は、ThreadLocalで も な2つの です。
ThreadLocalの の な は、ThreadLocal はデータを していません。データは のスレッドで されているThreadLocal Mapに されています。ThreadLocal Mapは、 な クラスとして、ThreadLocalクラスで されています。 のスレッドは、このThreadLocal Mapを して しています。ThreadLocal は、 にkeyとして しています。 スレッドのThreadLocal Mapはプライベートですので、ThreadLocalはスレッド のデータ をうまく できます。
ThreadLocalの はすべてThreadLocal Mapに づいて われるので、ThreadLocalのソースコードを む に、まずThreadLocal Mapの を に きます。
3.1、ThreadLocal Map
まず、ThreadLocal Mapのトップコメントを てください。
    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
  から かります。
  • ThreadLocal Mapは、ThreadLocalによって されたhashハッシュマップであり、 のHashMapとは なり、スレッドローカル のみを するために されます。
  • そのすべての は、ThreadLocalの によってのみ、 する を び すことができます。
  • ハッシュ・テーブルの の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;
                }
            }
     ThreadLocal Mapのハッシュテーブルのキーペアの は、keyがThreadLocalオブジェクトの い であるため、keyが から く されない 、 するキーペアはすでに が れています。ハッシュテーブルから することができます。ThreadLocal MapがThreadLocalの をkeyとして っています。もしThreadLocalが からの がないなら、システムgcの に、このThreadLocalは ず されます。そうすると、ThreadLocal Mapの にkeyがnullのEntryが れます。これらのkeyをnullのEntryスレッドにアクセスできなくなります。これらのkeyがnullのEntryのvalueであると、 い チェーンがずっと します。「Thread Ref-」Thread->Threa Local Map->Entry->value」は に できなくなり、メモリが れてしまいます。
    JDKは、ThreadLocal Mapのget Entry() またはset() を び して、これらのnull keyのentryをクリアしますが、まだ りません。これらの は で び さないかもしれませんので、この はどんな でも できます。
    したがって、 くの 、 は、ThreadLocalのremove を で び す があり、 なThreadLocalを で し、メモリの を ぐ。JDKは、ThreadLocal をprvate staticと することを しています。そうすると、ThreadLocalのライフサイクルはもっと くなります。ThreadLocalの い がずっと していますので、ThreadLocalは されません。また、いつでもThreadLocalの い によってEntryのvalue にアクセスできます。
    スレッドがなかなか わらない 、つまりThreadLocalのオブジェクトは されません。 えば、スレッドプールの 、メモリが れます。(メモリリークのポイント)。
     
    3.2、ThreadLocalの
    に、ThreadLocalの も ないくつかの を ます。
    3.2.1、
        /**
         * Creates a thread local variable.
         * @see #withInitial(java.util.function.Supplier)
         */
        public ThreadLocal() {
        }
     
    3.2.2、set()の
    /**
         * Sets the current thread's copy of this thread-local variable
         * to the specified value.  Most subclasses will have no need to
         * override this method, relying solely on the {@link #initialValue}
         * method to set the values of thread-locals.
         *
         * @param value the value to be stored in the current thread's copy of
         *        this thread-local.
         */
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    このスレッドのローカル の で、 のスレッドのコピーの を の に します。
  • は のスレッドを する。
  • のスレッドの のThreadLocal Mapを つけました。
  • のスレッドのThreadLocal Mapが されていない 、ThreadLocal Mapを しながらデータを します。そうでなければ、 のスレッドに って するkey-valueを します。
  •  セット() はget Map()、set()、createMap()に しています。
    get Map():
        /**
         * Get the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param  t the current thread
         * @return the map
         */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    : のスレッドオブジェクトを し、 するThreadLocal Mapを します。
    セット():
            /**
             * Set the value associated with key.
             *
             * @param key the thread local object
             * @param value the value to be set
             */
            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;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    :ハッシュアルゴリズムによると、 スレッドオブジェクトで されているThreadLocal Mapに する を つけ、キーペアを する( な は のThreadLocalの で しく する)。
    createMap():
        /**
         * Create the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the map
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
    :スレッド で されているThreadLocal Mapがまだ されていない(Setを び すのは めて) にトリガされ、カレントスレッド のthreadLocal MapオブジェクトにTrreadLocars を し、 のキーペアを します。
    3.2.3、get()
        /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        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();
        }
     このスレッドのローカル の のスレッドのコピーの を します。 に のスレッドの がない は、まず、@linkメソッドを び して された に する。
    3.2.4、Remove()
        /**
         * Removes the current thread's value for this thread-local
         * variable.  If this thread-local variable is subsequently
         * {@linkplain #get read} by the current thread, its value will be
         * reinitialized by invoking its {@link #initialValue} method,
         * unless its value is {@linkplain #set set} by the current thread
         * in the interim.  This may result in multiple invocations of the
         * {@code initialValue} method in the current thread.
         *
         * @since 1.5
         */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    :Thread のメンテナンスのThreadLocal Mapがnullでない 、keyに するキーのペアを します。

    JDK 1.8ソース