探究ThreaLocal

前言

    ThreadLocal提供一個(只有一個)線程的局部變量,爲了確保多線程環境下,線程的安全性。其實可以這樣理解,ThreadLocal其實就是一個普通類,它聲明的對象有明確的作用範圍,這個範圍就是用ThreadLocal去聲明對象的線程,當然每個線程可以有多個ThreadLocal變量,這些ThreadLocal變量被保存在ThreadLocalMap中,這個ThreadLocalMap其實相當於一個key-value結構的Map,結構大概是這樣ThreadLocalMap<ThreadLocal,Object>。

例子

/**
 * @author: ggp
 * @Date: 2019/7/2 20:56
 * @Description:
 */
public class ThreadLocalTest {
    public static void main(String[] args) {
       ThreadLocal<Number> t = new ThreadLocal<>();
       Thread thread1 = new Thread(new Runnable() {
           @Override
           public void run() {

              for(int i =0; i<100;i++){
                  if(null == t.get()){
                      t.set(new Number());
                  }
                  t.get().setVal(i);
                  System.out.println(Thread.currentThread().getName()+">>>>>>>>>>"+t.get().getVal());
              }
           }
       });
       thread1.start();
       Thread thread2 = new Thread(new Runnable() {
           @Override
           public void run() {
               for(int i=1000;i<1100;i++){
                   if(null == t.get()){
                       t.set(new Number());
                   }
                   t.get().setVal(i);
                   System.out.println(Thread.currentThread().getName()+">>>>>>>>>>"+t.get().getVal());
               }
           }
       });
       thread2.start();
    }
}
class Number{
    int val = 2;

    public int getVal() {
        return val;
    }

    public void setVal(int val) {
        this.val = val;
    }
}

運行結果

可以看到兩個線程互不干擾,確保了多線程之間的線程安全。

源碼分析

threadLocal.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();
    }

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

   首先先獲取到當前線程t,然後拿到線程的局部變量表ThreadLocalMap

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

 跟蹤源碼可以看出ThreadLocalMap是 ThreadLocal的一個靜態內部類,而Entry是ThreadLocalMap的基本組成單位

static class ThreadLocalMap {

        /**
         * 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<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

可以看到作者的註釋,這個entry實現了弱引用的接口,而這個弱引用指向hashmap中的key值,所以當key值爲null時,GC的時候就會回收key值,但是value並不是弱引用,所以不會被回收。entry沒法被調用卻也佔着內存,就會造成內存泄露,直到當前線程結束,線程被回收,內存纔會被完全釋放。但是如果這個場景發生在線程池,那麼內存泄露就會一直存在,直到應用結束。

但是,人家jdk早就有了解決方法。。。。。。。。。。

在entry的get和set方法中會做一個判斷來清楚這些隱患

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

以get爲例,計算hash值,當拿到的entry中的key爲空的時候進入 getEntryAfterMiss()中的expungeStaleEntry()方法中,通過源碼可以看出,首先是將value置空,然後把整個entry置空,最後重新hash。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章