線程基礎知識08- ThreadLocal基礎總結


     ThreadLocal在平時開發中是用的比較多的一個類,主要用於存儲線程的數據。下面我對ThreadLocal進行一下總結;

     dreamcatcher-cx大佬對ThreadLocal的設計總結,寫的比較深刻,很有幫助。

主要使用場景:

  • 多數據源切換,記錄當前線程訪問的數據源

  • spring框架事務管理,用於存放事務數據;

  • springsecurity安全框架,用於存儲用戶登錄信息;

除了以上還有很多,不限於以上。

解讀源碼

數據存儲ThreadLockMap

  • 通過Entry數組進行存儲的;

  • Entry繼承Reference類,是弱關聯的類,當ThreadLocal的實例為空時,GC會快速收回;一般用於處理數據量較大且維持時間較短的業務。

  • Entry節點存儲的是ThreadLocal對象和對象值

//是通過Entry數組進行存儲的 
private Entry[] table;

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

set 方法

  • 存放ThreadLocal<?>的是弱關聯的容器,GC會等存儲滿的時候處理;

  • 存放的過程中,會對key判斷,調整數組位置;

 private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);//獲取存放的位置

            /**
             * 當i位置剛好已經存儲了數據
             */
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {//向i+1<len遍歷
                ThreadLocal<?> k = e.get();//獲取存入的ThreadLock
                if (k == key) {//判斷是否一致,如果一致,替換值並返回
                    e.value = value;
                    return;
                }

                if (k == null) {//如果i節點獲取到的ThreadLocal為空的話               
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //沒有存儲數據的情況
            tab[i] = new Entry(key, value);//新增信的節點
            int sz = ++size;
            /**
             * 判斷是不是要進行擴容
             * Entry數組的擴容是2倍擴容的;            
             */
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }



     private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // 判斷i之前的非空節點的位置
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            //從staleSlot又向后循環,找到非空節點,查看是否又相同key
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                /**
                 * 判斷和查詢的實例是否同一個,如果key相同,則和staleSlot節點進行替換位置   
                 */ 
                  if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);//這里進行rehash
                    return;
                }

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // 如果key沒找到那就在staleSlot位置插入一個新節點
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // 判斷是不是要重新盡心hash
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

get方法

  • 獲取存儲的Entry節點
   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
                /**
                 * 從i位置往后逐個遍歷查找如果找到了就返回,沒找到返回null;
                 */   
                return getEntryAfterMiss(key, i, e);
        }

remove方法

  • 查找對應數據節點進行刪除就行
  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;
                }
            }
        }

ThreadLocal類的方法

set方法數據存儲

  • 首次存儲,新建一個ThreadLockMap,以當前ThreaLock為key;

  • 如果線程的存儲區域已經初始化過,則更新存儲區域中的數據;

public void set(T value) {
        Thread t = Thread.currentThread();// 獲取當前線程
        ThreadLocalMap map = getMap(t);//獲取存儲數據的MAP
        if (map != null)//判斷存儲數據的map是否為空,如果為空則創建map,如果不為空,則存入數據
            map.set(this, value);
        else
            createMap(t, value);//如果沒有就創建一個,當前Thread
    }
//獲取當前線程的值存儲區域
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; //線程的成員變量
    }
//給當前線程創建一個新的ThreadLocalMap,並存儲以當前ThreaLocal實例為key的鍵值對。
 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

get方法

  • 如果在訪問之前沒有存儲任何數據,則對ThreadLockMap進行初始化,綁定當前線程。並進行初始值的創建,和當前ThreadLock實例進行綁定,kv存儲

  • 如果獲取到之前存儲的值,則進行返回存入的值;

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//獲取當前線程的存儲map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//獲取當前的ThreadLock的節點,具體上面有介紹
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();//如果不存在則進行初始化
    }


//進行一些初始化操作
private T setInitialValue() {
        T value = initialValue();//ThreaLock提供的一個可繼承的方法,進行初始化操作
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//獲取當前線程的
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

remove 方法

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)//在存儲的map不為空的情況下,移除當前ThreadLock實例
             m.remove(this);
     }

總結

注意點:

1. ThreadLocalMap存儲的key值為當前的ThreadLocal實例,所以一般要求ThreadLocal是單例的

2. 單個線程可以有多個ThreadLocal實例,存儲數據。

總結分析:

1.ThreadLocal的設計思路:

  • 原理:相當給當前線程的創建獨有數據域,方便當前線程訪問。這樣避免了多線程環境中,訪問當前線程鎖的相關問題

  • 之所以使用ThreadLocalMap進行存儲,而不用HashMap,上面源碼分析說了,源於Reference類的特性。

    • 弱引用,當對象無用的時候,會快速被GC回收。符合線程的特點,一般一個線程時間不會很長,當伴隨大量數據時能快速回收

    • 當前線程結束的時候,要通過調用ThreadLocal的remove方法,及時將ThreadLocalMap中的對應實例key設置為空,方便GC快速回收

  • ThreadLocal操作更有優勢,下面我具體分析以下

為什么用ThreadLocal類直接操作存儲數據 ?

1.簡化代碼:

試想一下上面的源碼,如果直接從Thread中存放數據,
首先,要用Thread.currentThread()獲取當前線程,
然后,再通過當前線程thread獲取存儲的Map
再根據key獲取對那個的操作

從上面的源碼可以看出,這些操作細節,都已經在ThreadLocal類中進行了隱藏;
1.避免每次使用時的重復代碼;
2.而使用ThreadLocal實例獲取數據,相當於拿key直接獲取數據,隱藏了獲取細節

2.限制和統一管理存儲值

1. 內部的Entry數組,進行了泛型約束,避免了直接在Thread類中Map操作key的不規范性;

2. 避免了開發人員直接操作當前線程,而使用“變量副本”進行操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM