ThreadLocal原理與使用場景

一、ThreadLocal原理

如果看懂了ThreadLocal的set()方法,get()、remove()方法也就好理解了,所以重點看一下set()方法。

set()方法執行流程總結:

1.獲取當前線程對象

2.獲取當前線程對象的成員變量ThreadLocalMap

3.1不爲null,set值

3.1.1獲取當前ThreadLocalMap對象的Entry數組

3.1.2獲取ThreadLocal對象的i值

3.1.3遍歷Entry數組,根據ThreadLocal對象的i值判斷,如果ThreadLocal對象已存在數組中,更新值,退出

3.1.4不存在數組中,以key-value形式:將ThreadLocal對象與值value綁定放入數組中

3.2爲null,爲當前線程創建成員變量ThreadLocalMap

3.2.1 new一個Entry數組,初始容量爲INITIAL_CAPACITY=16

3.2.2計算出存放在Entry數組的位置i:ThreadLocal的threadLocalHashCode與15的位運算,結果與取模相同。

3.2.3放入Entry數組

源碼分析如下:

public class Thread implements Runnable {
    //每個線程對象都維護了一個成員變量ThreadLocalMap
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    public T get() {}
    public void remove() {}
    public void set(T value) {
        Thread t = Thread.currentThread();//當前線程對象
        ThreadLocal.ThreadLocalMap map = getMap(t);//獲取當前線程對象的成員變量ThreadLocalMap
        if (map != null)
            map.set(this, value);//不爲null,set值
        else
            createMap(t, value);//爲null,爲當前線程創建ThreadLocalMap
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);//爲線程對象創建成員變量ThreadLocalMap
    }

    static class ThreadLocalMap {
        private static final int INITIAL_CAPACITY = 16;
        static class Entry extends WeakReference<ThreadLocal<?>> {
            //線程實際存儲私有數據的地方
            Object value;
            //以key-value形式,將ThreadLocal與Object對象綁定。一個ThreadLocal對象在一個線程Thread對象中只可存儲一個Object。
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        //ThreadLocalMap類內部維護了一個Entry數組對象table
        private ThreadLocal.ThreadLocalMap.Entry[] table;

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];//new一個Entry數組,初始容量爲INITIAL_CAPACITY=16
            /**
             * 計算出存放在table數組的位置:ThreadLocal的threadLocalHashCode與15的位運算,結果與取模相同。
             * 因爲每個ThreadLocal對象創建後,threadLocalHashCode是final不變的,所以每個ThreadLocal對象的i值是不變的,
             * 這也是爲什麼一個ThreadLocal對象在一個線程Thread對象中只可存儲一個Object的原因
             */
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);//放入Entry數組
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        private void set(ThreadLocal<?> key, Object value) {
            ThreadLocal.ThreadLocalMap.Entry[] tab = table;//當前ThreadLocalMap對象的Entry數組
            int len = tab.length;
            int i = key.threadLocalHashCode & (len - 1);//ThreadLocal對象的i值
            //遍歷Entry數組
            for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //ThreadLocal對象已存在數組中,更新值,退出
                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //不存在數組中,以key-value形式:將ThreadLocal與value綁定放入數組中
            tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    }
}

根據上面源碼總結如下:

一個Thread對象可有多個ThreadLocal對象在線程的本地內存中存儲值,一個ThreadLocal對象可在多個線程的本地內存中存儲值。所以Thread與ThreadLocal可簡單理解爲多對多關係。(線程的本地內存也稱爲線程的工作內存,JMM內存模型定義的概念)

ThreadLocal的本質就是一個工具類,提供了set()、get()、remove()等方法,提供對線程本地內存的值的操作。Thread的成員變量ThreadLocalMap實現線程本地內存數據存儲的(重點:成員變量實現本地內存對象的存儲),ThreadLocalMap是定義在ThreadLocal的靜態內部類。

ThreadLocalMap維護一個Entry類型數組,Entry是定義在ThreadLocalMap的靜態內部類,Entry就是用了一個Object類型的成員變量value來存儲值的,而且Entry的構造函數還將本ThreadLocal對象與線程值value以key-value形式綁定了。一個ThreadLocal對應Entry[]數組的下標i是唯一不變的,因爲ThreadLocal的hash值是final。

二、ThreadLocal使用場景

Synchronized和ThreadLocal都是用來解決多線程併發數據訪問的,但是Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。所以,使用場景要根據這個變量本身是否應該是共享的!

ThreadLocal常用使用場景爲解決數據庫連接、Session管理等。可參考spring的管理。
例子:一個變量是成員變量(多線程共享的),但它本質上應該是線程私有的,就可用ThreadLocal將共享成員變量copy一份到線程私有變量中。如下Demo:

/**
* 問題:如何做到 MyThreadLocal單例,sendMessage()方法不加鎖,但是多線程執行sendMessage()仍然是線程安全的?
* 答:共享成員變量connection設爲線程私有。即使用ThreadLocal將共享成員變量變成線程私有
**/
public class MyThreadLocal {
    //成員變量,多線程共享
    private Connection connection;

    public static ThreadLocal<Connection> threadLocal = new ThreadLocal();

    public void sendMessage() throws SQLException {
        //Statement statement = connection.createStatement();
        Statement statement = getConnection().createStatement();
        //...
    }

    public static Connection getConnection() {
        Connection conn = threadLocal.get();
        if (conn == null) {
            Connection conn = ConnectionManager.getConnection();
            threadLocal.set(conn);
            return conn;
        } else {
            return conn;
        }
    }
}

 

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