java ThreadLocal線程設置私有變量底層源碼分析

  前面也據說了ThreadLocal來實現高併發,之前都是用鎖來實現,看了挺多資料的,發現其實仍是區別挺大的(感受嚴格來講ThreadLocal並不算高併發的解決方案),如今總結一下吧。java

  高併發中會出現的問題就是線程安全問題,能夠說是多個線程對共享資源訪問如何處理的問題,處理不當會的話,會出現結果和預期會徹底不一樣。web

  通常狀況下,多個線程訪問一個變量都是公用他們的值,不過有時候雖然也是訪問共享變量,不過每一個線程卻須要本身的私有變量。這個時候ThreadLocal就有用武之地了。下面是個ThreadLocal的簡單實例:spring

 

public class ThreadLocalExample {
    public static void main(String[] args){
        //建立一個ThreadLocal對象
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        
        //設置主線程私有變量值
        threadLocal.set(100);
        
        //建立一個新線程
        new Thread(new Runnable(){
            public void run(){
                //使用共享變量,設置線程私有變量
                threadLocal.set(50);
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        }).start();
        
        System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
    }
}

 

輸出結果:數據庫

main:100
Thread-0:50

  很神奇,對多個資源之間的共享,又不想他們之間相互影響,因此使用這個是挺不錯的。具體應用,spring中應用我記得挺多的,鏈接數據庫的每一個鏈接,還有session的存儲。數組

  思考了一下,要我實現的話就用個map來存儲,由於這個其實就是鍵值對,只不過鍵是線程惟一標識,值就是對應的私有變量。安全

具體看了源碼發現差很少,不過使用內部本身實現的一個ThreadLocalMap類,內部還一個Entry類並且Entry類繼承weakRefrence(說實話第一次遇到弱應用,之前只是在jvm那本書學習了下),具體方法以下:session

  

先看下他的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);
}

getMap方法的話一個得到全部線程共享的ThreadLocalMap對象以下:jvm

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

而後進入Thread類進去找一下這個容器,找到下面:高併發

ThreadLocal.ThreadLocalMap threadLocals = null;

而後建立:

void createMap(Thread t, T firstValue) {
    //建立ThreadLocalMap對象賦給threadLocals t.threadLocals
= new ThreadLocalMap(this, firstValue); }

至此,ThreadLocal的基本原理就已經很清晰了:各線程對共享的ThreadLocal實例進行操做,其實是以該實例爲鍵對內部持有的ThreadLocalMap對象進行操做

還有get()方法的話就是利用設置的鍵進行獲取,remove()方法也是,其實和Hashmap差很少不過解決衝突使用的拉鍊法(對了,下次寫一篇HashHap的還有ConcurrentHashMap的話,很有研究)。這裏有個問題就是由於這個ThreadLocalMap是靜態的因此在方法區中(jdk8以後爲元數據區),不進行回收的話會形成內存泄漏,並且可能會出現內存溢出,因此使用後記得remove();

基本上其實能夠了,不過好奇ThreadLocalMap怎麼實現的能夠接着往下看,我也好奇,因此也偷偷看了,嘿嘿嘿

 

那就來分析一下這個ThreadLocalMap這個內部類吧。

ThreadLocalMap屬於一個自定義的map,是一個帶有hash功能的靜態內部類,其實和java.util包下提供的Map類並無關係。內部有一個靜態的Entry類,下面具體分析Entry。

/**
         * 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繼承自WeakReference,用main方法引用的字段做爲entry中的key。當entry.get() == null的時候,意味着鍵將再也不被引用。

注意看到一個super(k),說明調用父類的構造,去看看

Reference(T referent) {
    this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

  就上面這個其餘沒了,看了半天有點沒看懂,而後去學了四種引用回來終於看懂,因爲篇幅過多,在結尾我給出兩篇別人的博客,能夠去看完了,再回來,多學點哈哈哈。

再看了下發現這個內部類好多,可是其實就是map的一種實現,上面也講了set方法那就簡單提一下ThreadLocalMap的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對象數組拿到來
            ThreadLocal.ThreadLocalMap.Entry[] tab = table;
            //長度也拿到來
            int len = tab.length;
            //經過拿到key的hashcode值,進去發現神奇的一幕這裏利用經過累加這個值0x61c88647來做爲hashcode,
            // 這裏提一下往下走發現由於要公用這個屬性,多個實例訪問會有問題
            // 因此使用了AtomicInteger原子操做來寫值
            //而且與總長度-1作與運算就是取模,由於擴容都是2的n次方因此這樣直接取模就行,速度快
            int i = key.threadLocalHashCode & (len-1);

            //定位到對應的數組位置,進行衝突判斷之類的處理
            for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {   //這裏是衝突遍歷

                //這裏裏面就拿對應tabel下對應位置的當前引用
                ThreadLocal<?> k = e.get();

                //判斷是否是對應的鍵,是的話就覆蓋
                if (k == key) {
                    e.value = value;
                    return;
                }
                //沒有的話就生成Entry代替掉
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //這裏就直接插入了
            tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
            //長度加1
            int sz = ++size;
            //判斷是否作擴容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

 

裏面其實挺複雜的,具體的話就是正常是使用開放定址法處理,這裏使用累加一個定值解決的衝突,由於多個實例共用,特殊處理,厲害厲害。

//threadLocalHashCode代碼也貼在這裏吧,有興趣能夠直接去看

        private static AtomicInteger nextHashCode = new AtomicInteger();
        private static final int HASH_INCREMENT = 0x61c88647;
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }
        private final int threadLocalHashCode = nextHashCode();

 總結

看完源碼以後神清氣爽,學到了不少啦。之前對java引用只是知道四個引用和對應的相應簡單概念,爲了看懂這個Entry,去學習了weakReference源碼,看了別人的關於四個引用的博客寫的真好,偷偷學習了下,而且知道怎麼使用了。劃重點會用了!!!固然對於ThreadLocal也會用了,並且好像能夠手寫一個簡單的版本哎,能夠動手試試。

 

關於四種引用博客,寫的真的很棒。

https://blog.csdn.net/swebin/article/details/78571933

http://www.javashuo.com/article/p-uwnfclcl-a.html

相關文章
相關標籤/搜索