天天看點

ThreadLocal

ThreadLocal

對象是線程的局部變量,每個線程都能在其中儲存隻屬于自己的内容。對于同一個static ThreadLocal,不同線程隻能從中get,set,remove自己的變量,而不會影響其他線程的變量。

結構體系大概如下:

ThreadLocal

ThreadLocal如何保證這些變量隻被目前線程所通路的呢?

ThreadLocal.set

首先看一下ThreadLocal的set():

public class ThreadLocal<T> {

    /* 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;
    
    /**
     * 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);
    }
}           

set時,首先擷取目前線程對象,然後通過getMap擷取線程的ThreadLocalMap,并将值設定ThreadLocalMap中,ThreadLocalMap可以了解為一個Map。map.set()如下:

static class ThreadLocalMap {
        /**
         * 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();
        }
    }           

方法中循環了map中的元素,對map中的key和入參key比較,如果相同則賦新值,如果map中key存在null值則替換,否則重新生成一個map元素。

如果沒有map,則執行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);
    }           

createMap中構造了ThreadLocalMap:

/**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }           

在這裡,為Thread的threadLocals指派。

ThreadLocal.get

而設定到ThreadLocal中的資料,實質上是寫入了Thread的threadLocals屬性。其中key為ThreadLocal目前對象,value就是我們需要的值。Thread的threadLocals儲存了目前自己所線上程的所有“局部變量”,也就是一個ThreadLocal變量的集合。

在進行get操作時,自然就是将這個Map中的資料拿出來:

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

首先,get方法也是先取得目前線程的ThreadLocalMap對象,然後,将自己傳入作為getMap的參數,而getMap傳回的則是目前線程的threadLocals屬性:

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

即目前線程的threadLocals屬性,也就是說,ThreadLocal類的getMap方法傳回的是Thread類的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;           

是以,threadLocals是維護在Thread内部的,這意味着隻要線程不退出,對象的引用就一直存在。

當線程退出時會進行一些清理工作,其中包括對ThreadLocalMap的清理:

/**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }           

如果使用線程池的話,線程未必會退出,此時将一些比較大的對象放入ThreadLocal中(實際儲存線上程持有的threadLocals Map中),可能會導緻記憶體洩漏的問題。這時可以使用ThreadLocal.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);
     }           

執行個體示範

public class Test {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static ThreadLocal<String> anotherThreadLocal = new ThreadLocal<>();
    static AtomicInteger atomicInteger = new AtomicInteger(0);

    static class ThreadDemo implements Runnable {

        @Override
        public void run() {
            if (threadLocal.get() == null) {
                threadLocal.set("線程:" + atomicInteger.addAndGet(1));
            }
            if (anotherThreadLocal.get() == null) {
                anotherThreadLocal.set("anotherThreadLocal:" + atomicInteger.get());
            }
            System.out.println(threadLocal.get());
            System.out.println(anotherThreadLocal.get());
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ThreadDemo());
        }
    }
}           

表明,一個ThreadLocal可以供多個線程共享,但是每個線程隻能拿到屬于自己的那一份存儲。每一個線程的threadLocals的大小可以為多個,每一個ThreadLocal對象都可以會作為該map的key存在。