天天看點

源碼解讀 | JDK源碼 | ThreadLocal 實作原理

核心提煉

  • ​Thread​

    ​ 類有維護了一個屬性變量 ​

    ​threadLocals​

    ​ (ThreadLocal.ThreadLocalMap threadLocals = null),也就是說每個線程有都一個自己的 ​

    ​ThreadLocalMap​

    ​ ,是以每個線程往這個 ​

    ​ThreadLocal​

    ​ 中讀寫隔離的,并且是互相不會影響的 。
  • ​ThreadLocalMap​

    ​ 類是 ​

    ​ThreadLocal​

    ​ 的靜态内部類
  • ​ThreadLocalMap​

    ​ 維護了一個 ​

    ​Entry​

    ​ 數組,​

    ​Entry​

    ​ 的 key 是 ​

    ​ThreadLocal​

    ​ 對象,value 是存入的對象,是以一個 ​

    ​ThreadLocal​

    ​ 隻能存儲一個Object對象,如果需要存儲多個Object對象那麼就需要多個 ​

    ​ThreadLocal​

  • ​Entry​

    ​ 的 key 引用 ​

    ​ThreadLocal​

    ​ 是弱引用
  • ​Thread​

    ​、​

    ​ThreadLocalMap​

    ​ThreadLocal​

    ​總覽圖如下
源碼解讀 | JDK源碼 | ThreadLocal 實作原理
源碼解讀 | JDK源碼 | ThreadLocal 實作原理

ThreadLocal 是用來幹嘛的

ThreadLocal 主要是用在多線程的場景中

  • 儲存線程上下文資訊,在任意需要的地方可以擷取 (比如下面案例中的儲存使用者資訊)
  • 線程安全,避免某些情況需要考慮線程安全必須同步帶來的性能損失

使用場景案例

在spring MVC開發中,我們常用 ThreadLocal 儲存目前登陸使用者資訊,這樣線程在任意地方都可以取到使用者資訊,比如我們會有以下類似下面的 ​

​UserContext​

​ 類,然後給配置一個攔截器,攔截器裡面在請求執行前調用 ​

​UserContext​

​ 的 ​

​setUserInfo​

​ 方法将使用者資訊存入 ​

​ThreadLocal​

​ 對象 ​

​userInfoLocal​

​ 中,然後在請求的具體執行的任意地方調用 ​

​UserContext​

​getUserInfo​

​ 方法取出使用者資訊,最後在攔截器裡面在請求結束傳回前調用 ​

​UserContext​

​clear​

​ 方法清除資料

public class UserContext {
    private static final ThreadLocal<UserInfo> userInfoLocal = new ThreadLocal<UserInfo>();

    public static UserInfo getUserInfo() {
        return userInfoLocal.get();
    }

    public static void setUserInfo(UserInfo userInfo) {
        userInfoLocal.set(userInfo);
    }

    public static void clear() {
        userInfoLocal.remove();
    }

}      

ThreadLocal 使用代碼示例

public class ThreadLocalTest {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    threadLocal.set(i);
                    System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                threadLocal.remove();
            }
        }, "thread-1").start();

        new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                threadLocal.remove();
            }
        }, "thread-2").start();
    }
}      

運作結果

thread-1====0
thread-2====null
thread-2====null
thread-1====1
thread-2====null
thread-1====2
thread-2====null
thread-1====3
thread-2====null
thread-1====4      

從運作結果可以看出,​

​thread-1​

​線程中對​

​threadLocal​

​對象的指派對​

​thread-2​

​線程中​

​threadLocal​

​對象的值任何影響

源碼細節

Thread 類

public class Thread implements Runnable {
  /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}      

​Thread​

​類有屬性變量 ​

​threadLocals​

​ (類型是ThreadLocal.ThreadLocalMap),也就是說每個線程有一個自己的 ​

​ThreadLocalMap​

​ ,是以每個線程往這個​

​ThreadLocal​

​中讀寫隔離的,并且是互相不會影響的

一個ThreadLocal隻能存儲一個Object對象,如果需要存儲多個Object對象那麼就需要多個ThreadLocal

ThreadLocal 類

類簽名

public class ThreadLocal<T> {

}      

關鍵方法 | set

public void set(T value) {
        Thread t = Thread.currentThread();
            // ** 取出目前線程維護的 ThreadLocalMap 對象 **
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

// 建立一個 ThreadLocalMap 指派為目前 Thread 對象的屬性,并添加第一個 Entry
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }      

關鍵方法 | get

public T get() {
        Thread t = Thread.currentThread();
            // ** 取出目前線程維護的 ThreadLocalMap 對象 **
        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();
    }

// 取出線程維護的 ThreadLocalMap 對象
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }      

其他方法

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }      

ThreadLocalMap 類

​ThreadLocalMap​

​是​

​ThreadLocal​

​的靜态内部類

public class ThreadLocal<T> {
    // ThreadLocalMap 是 ThreadLocal 的靜态内部類
    static class ThreadLocalMap {

        // Entry 是 ThreadLocalMap 的靜态内部類
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            // Entry 的 key 是 ThreadLocal 對象,value 是關聯的資料對象
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        // ThreadLocalMap 維護了一個 Entry 數組,
        private Entry[] table;
    }    
}      

構造方法

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

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            // 如果 ThreadLocal 在 Entry 數組中已經存在,則替換其 value
            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;
                }
            }

            // 不存在,則建立一個 Entry 插入到 Entry數組中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }      

關鍵方法 | getEntry

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

/**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }      

ThreadLocalMap 裡 Entry 為何聲明為 WeakReference?

// Entry 是 ThreadLocalMap 的靜态内部類
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

WeakReference是什麼

  1. 強引用(StrongReference):被強引用關聯的對象不會被回收
  2. 軟引用(SoftReference):被軟引用關聯的對象隻有在記憶體不夠的情況下才會被回收
  3. 弱引用(WeakReference):被弱引用關聯的對象一定會被回收,也就是說它隻能存活到下一次垃圾回收發生之前
WeakReference 是 Java 語言規範中為了差別直接的對象引用(程式中通過構造函數聲明出來的對象引用)而定義的另外一種引用關系。WeakReference 标志性的特點是:reference 執行個體不會影響到被引用對象的 GC 回收行為(即隻要對象被除 WeakReference 對象之外所有的對象解除引用後,該對象便可以被 GC 回收),隻不過在被對象回收之後,reference 執行個體想獲得被應用的對象時程式會傳回 null。

為什麼ThreadLocalMap的key用弱引用,為什麼不用強引用呢?

看回這個例子

public class UserContext {
    private static final ThreadLocal<UserInfo> userInfoLocal = new ThreadLocal<UserInfo>();

    public static UserInfo getUserInfo() {
        return userInfoLocal.get();
    }

    public static void setUserInfo(UserInfo userInfo) {
        userInfoLocal.set(userInfo);
    }

    public static void clear() {
        userInfoLocal.remove();
    }

}      

此時 Entry 的情況是

key instance of WeakReference<ThreadLocal<UserInfo>>
value instance of UserInfo      

WeakReference 對引用的對象 userInfoLocal 是弱引用,不會影響到 userInfoLocal 的 GC 行為。 如果是強引用的話,線上程運作過程中,我們不再使用 userInfoLocal 了,将 userInfoLocal 置為 null,但 userInfoLocal 線上程的 ThreadLocalMap 裡還有引用,導緻其無法被GC回收(當然,可以等到線程運作結束後,整個Map都會被回收,但很多線程要運作很久,如果等到線程結束,便會一直占着記憶體空間)。 而 Entry 聲明為 WeakReference,userInfoLocal 置為 null 後,線程的 threadLocalMap 就不算強引用了,userInfoLocal 就可以被GC回收了。map的後續操作中,也會逐漸把對應的"stale entry"清理出去,避免記憶體洩漏。

為什麼value不用弱引用呢?

set/get/remove方法,這些方法會對key為null的entry進行釋放

繼續閱讀