ThreadLocal 剖析

SunYuLong發表於2018-09-21

  1. ThreadLocal是什麼?ThreadLocal有什麼好處?
  2. ThreadLocal會產生記憶體洩漏嗎?
  3. 當儲存Value的時候發生衝突怎麼辦?
  4. 為什麼在ThreadLocalMap中弱引用Entry呢?

  • 官方描述: 該類提供了執行緒區域性 (thread-local) 變數。這些變數不同於它們的普通對應物,因為訪問某個變數(通過其 get 或 set 方法)的每個執行緒都有自己的區域性變數,它獨立於變數的初始化副本。ThreadLocal 例項通常是類中的 private static 欄位,它們希望將狀態與某一個執行緒(例如,使用者 ID 或事務 ID)相關聯。

  • 自己的理解 ThreadLocal執行緒本地變數。為每個執行緒建立一個變數副本。 TODO

內部結構

ThreadLocal 剖析
  • ThreadLocal由ThreadLocalMap<ThreadLocal,Object>組成,key為ThreadLocal;
  • 本質上:ThreadLocalMap包含了一個Entry陣列;

關鍵方法

1. ThreadLocal#set()

    public void set(T value) {
        Thread t = Thread.currentThread();
        //重要:根據執行緒獲取Map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

複製程式碼

根據當前執行緒獲取Map,如果Map為空,則createMap().

  • ThreadLocal#getMap()做了那些事情呢?
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;//返回一個**執行緒繫結**的ThreadLocalMap
    }
複製程式碼
  • ThreadLocal#createMap()做了那些事情呢?
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
複製程式碼

可以看出,其實Thread本身就內建了一個ThreadLocalMap。

那麼接下來的重點就是看ThreadLocalMap如何初始化了。

1.1 建立ThreadLocalMap過程(初始化過程)

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            //初始化一個長度=16的物件陣列。Entry extends WeakReference
            table = new Entry[INITIAL_CAPACITY];
            //確定Hash值
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //設值
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
    }
複製程式碼

1.2 建構函式,建立Entry並設值

(重要!所以用截圖)

image_1cnrgn0mi1ceqoch1gprpkv1i0738.png-100kB

擴充:

  1. 為什麼在ThreadLocalMap中弱引用Entry呢?
  2. 但是隻有Key是弱引用的,當發生下一次GC時,

1.3 如何設值呢?

麻煩點:如果Hash衝突了怎麼辦呢?

        private void set(ThreadLocal<?> key, Object value) {
            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();
        }
複製程式碼

解決方法: ThreadLocalMap解決Hash衝突的方式就是簡單的步長加1或減1,尋找下一個相鄰的位置。

2. ThreadLocal#get()

    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();
    }
複製程式碼

應用場景

資料庫連線、Session管理

參考

  1. ThreadLocal-面試必問深度解析
  2. 【Java併發程式設計】深入分析ThreadLocal(八)

相關文章