中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java多線程之ThreadLocal的原理是什么

發布時間:2023-05-06 11:03:13 來源:億速云 閱讀:218 作者:zzz 欄目:開發技術

今天小編給大家分享一下Java多線程之ThreadLocal的原理是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

    1、什么是 ThreadLocal:

    ThreadLocal,即線程本地變量,如果你創建了一個變量,那么訪問這個變量的每個線程都會有這個變量的本地拷貝,多個線程操作這個變量的時候,實際操作自己本地內存里面的變量,從而起到線程隔離的作用,避免了線程安全問題

    ThreadLocal 適用于無狀態,副本變量獨立后不影響業務邏輯的高并發場景,如果業務邏輯強依賴于變量副本,則不適合用 ThreadLocal 解決,需要另尋解決方案

    應用場景:

    數據庫連接池會話管理中使用

    2、ThreadLocal 的數據結構:

    在 JDK8 中,每個線程 Thread 內部都維護了一個 ThreadLocalMap 的數據結構,ThreadLocalMap 中有一個由內部類 Entry 組成的 table 數組,Entry 的 key 就是線程的本地化對象 ThreadLocal,而 value 則存放了當前線程所操作的變量副本。每個 ThreadLocal 只能保存一個副本 value,并且各個線程的數據互不干擾,如果想要一個線程保存多個副本變量,就需要創建多個ThreadLocal。

    一個 ThreadLocal 的值,會根據線程的不同,分散在 N 個線程中,所以獲取 ThreadLocal 的 value,有兩個步驟:

    第一步,根據線程獲取 ThreadLocalMap

    第二步,根據自身從 ThreadLocalMap 中獲取值,所以它的 this 就是 Map 的 Key

    當執行 set() 方法時,其值是保存在當前線程的 ThreadLocal 變量副本中
    當執行get() 方法中,是從當前線程的 ThreadLocal 的變量副本獲取。

    所以對于不同的線程,每次獲取副本值時,別的線程并不能獲取到當前線程的副本值,形成了線程的隔離,互不干擾。

    Java多線程之ThreadLocal的原理是什么

    3、ThreadLocal 的核心方法:

    ThreadLocal 對外暴露的方法有4個:

    1.initialValue()方法:返回為當前線程初始副本變量值。
    2.get()方法:獲取當前線程的副本變量值。
    3.set()方法:保存當前線程的副本變量值。
    4.remove()方法:移除當前前程的副本變量值

     1、set()方法:

    // 設置當前線程對應的ThreadLocal值
    public void set(T value) {
        Thread t = Thread.currentThread(); // 獲取當前線程對象
        ThreadLocalMap map = getMap(t);
        if (map != null) // 判斷map是否存在
            map.set(this, value); 
            // 調用map.set 將當前value賦值給當前threadLocal。
        else
            createMap(t, value);
            // 如果當前對象沒有ThreadLocalMap 對象。
            // 創建一個對象 賦值給當前線程
    }
     
    // 獲取當前線程對象維護的ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    // 給傳入的線程 配置一個threadlocals
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    執行流程:

    1.獲得當前線程,根據當前線程獲得 map。
    2.如果 map 不為空,則將參數設置到 map 中,當前的 Threadlocal 作為 key。
    3.如果 map 為空,則給該線程創建 map,設置初始值。

     2、get()方法:

    public T get() {
        Thread t = Thread.currentThread();//獲得當前線程對象
        ThreadLocalMap map = getMap(t);//線程對象對應的map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);// 以當前threadlocal為key,嘗試獲得實體
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果當前線程對應map不存在
        // 如果map存在但是當前threadlocal沒有關連的entry。
        return setInitialValue();
    }
     
    // 初始化
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    執行流程:

    (1)先嘗試獲得當前線程,再根據當前線程獲取對應的 map
    (2)如果獲得的 map 不為空,以當前 threadlocal 為 key 嘗試獲得 entry
    (3)如果 entry 不為空,返回值。
    (4)如果 2 跟 3 出現無法獲得,則通過 initialValue 函數獲得初始值,然后給當前線程創建新 map

     3、remove()方法:

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

    執行流程:

    首先嘗試獲取當前線程,然后根據當前線程獲得map,從map中嘗試刪除enrty。

    4、initialValue() 方法:

    protected T initialValue() {
        return null;
    }

    執行流程:

    (1)如果沒有調用 set() 直接 get(),則會調用此方法,該方法只會被調用一次,
    (2)默認返回一個缺省值null,如果不想返回null,可以Override 進行覆蓋。

    4、ThreadLocal 的哈希沖突的解決方法:線性探測

    和 HashMap 不同,ThreadLocalMap 結構中沒有 next 引用
    ThreadLocalMap 中解決哈希沖突的方式并非鏈表的方式,而是采用線性探測的方式,當發生哈希沖突時就將步長加1或減1,尋找下一個相鄰的位置

    流程說明:

    1.根據 ThreadLocal 對象的 hash 值,定位到 table 中的位置 i;

    2.如果當前位置是 null,就初始化一個 Entry 對象放在位置 i 上;

    3.如果位置 i 已經有 Entry 對象了,如果這個 Entry 對象的 key 與即將設置的 key 相同,那么重新設置 Entry 的 value;

    4.如果位置 i 的 Entry 對象和 即將設置的 key 不同,那么尋找下一個空位置;

    5、ThreadLocal 的內存泄露:

    在使用 ThreadLocal 時,當使用完變量后,必須手動調用 remove() 方法刪除 entry 對象,否則會造成 value 的內存泄露,嚴格來說,ThreadLocal 是沒有內存泄漏問題,有的話,那也是忘記執行 remove() 引起的

    內存泄露的根本原因在于 ThreadLocalMap 的生命周期與當前線程 CurrentThread 的生命周期相同,且 ThreadLocal 使用完沒有進行手動刪除導致的

    ThreadLocal 的內存泄露與強弱引用無關,那么為什么還要用弱引用呢?

    (1)Entry 中的 key(Threadlocal)是弱引用,目的是將 ThreadLocal 對象的生命周期跟線程周期解綁,用 WeakReference 弱引用關聯的對象,只能生存到下一次垃圾回收之前,GC發生時,不管內存夠不夠,都會被回收。

    (2)當我們使用完 ThreadLocal,而 Thread 仍然運行時,即使忘記調用 remove() 方法, 弱引用也會比強引用多一層保障:當 GC 發生時,弱引用的 ThreadLocal 被收回,那么 key 就為 null 了。而 ThreadLocalMap 中的 set()、get() 方法,會針對 key == null (也就是 ThreadLocal 為 null) 的情況進行處理,如果 key == null,則系統認為 value 也應該是無效了應該設置為 null,也就是說對應的 value 會在下次調用 ThreadLocal 的 set()、get() 方法時,執行底層 ThreadLocalMap 中的 expungeStaleEntry() 方法進行清除無用的 value,從而避免內存泄露。

    6、ThreadLocal 的應用場景:

    (1)Hibernate 的 session 獲取:每個線程訪問數據庫都應當是一個獨立的 session 會話,如果多個線程共享同一個 session 會話,有可能其他線程關閉連接了,當前線程再執行提交時就會出現會話已關閉的異常,導致系統異常。

    使用 ThreadLocal 的方式能避免線程爭搶session,提高并發安全性。

    (2)Spring 的事務管理:事務需要保證一組操作同時成功或失敗,意味著一個事務的所有操作需要在同一個數據庫連接上,Spring 采用 Threadlocal 的方式,來保證單個線程中的數據庫操作使用的是同一個數據庫連接,同時采用這種方式可以使業務層使用事務時不需要感知并管理 connection 對象,通過傳播級別,巧妙地管理多個事務配置之間的切換,掛起和恢復

    7、如果想共享線程的 ThreadLocal 數據怎么辦 ?

    使用 InheritableThreadLocal 可以實現多個線程訪問 ThreadLocal 的值

    我們在主線程中創建一個 InheritableThreadLocal 的實例,然后在子線程中得到這個InheritableThreadLocal實例設置的值。

    private void test() {    
    final ThreadLocal threadLocal = new InheritableThreadLocal();       
    threadLocal.set("主線程的ThreadLocal的值");    
    Thread t = new Thread() {        
        @Override        
        public void run() {            
          super.run();            
          Log.i( "我是子線程,我要獲取其他線程的ThreadLocal的值 ==> " + threadLocal.get());        
        }    
      };          
      t.start(); 
    }

    8、為什么一般用 ThreadLocal 都要用 static?

    ThreadLocal 能實現線程的數據隔離,不在于它自己本身,而在于 Thread 的 ThreadLocalMap,所以,ThreadLocal 可以只實例化一次,只分配一塊存儲空間就可以了,沒有必要作為成員變量多次被初始化。

    以上就是“Java多線程之ThreadLocal的原理是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    酒泉市| 盐亭县| 那曲县| 龙岩市| 忻州市| 称多县| 青州市| 桦甸市| 重庆市| 仙桃市| 威宁| 临澧县| 朝阳区| 资阳市| 睢宁县| 惠东县| 叶城县| 孙吴县| 长乐市| 丹寨县| 金昌市| 微博| 梧州市| 从江县| 买车| 耒阳市| 改则县| 济阳县| 邛崃市| 鹤山市| 蒙城县| 天峨县| 辽源市| 上犹县| 健康| 周口市| 漳平市| 九龙城区| 平南县| 武安市| 泰安市|