日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

弱引用使用场景桌面_面试|再次讲解Threadlocal使用及其内存溢出

發布時間:2025/3/12 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 弱引用使用场景桌面_面试|再次讲解Threadlocal使用及其内存溢出 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ThreadLocal徹底詳解

整理本文主要是想幫助大家完全消化面試中常見的ThreadLocal問題。希望讀懂此文以后大家可以掌握:

  • 簡單介紹原理
  • ThreadLocal使用案例場景
  • Threadlocal的底層原理
  • Threadlocal內存溢出原因和解決方法
  • 1. 簡介

    高并發處理起來比較麻煩,很多新手對此都會非常頭疼。要知道避免并發的最簡單辦法就是線程封閉,也即是把對象封裝到一個線程里,那么對象就只會被當前線程能看到,使得對象就算不是線程安全的也不會出現任何安全問題。Threadlocal是實現該策略的最好的方法。Threadlocal為每個線程提供了一個私有變量,然后線程訪問該變量(get或者set)的時候實際上是讀寫的自己的局部變量從而避免了并發法問題。

    2. 案例使用

    首先定義一個ThreadLocal的封裝工具類

    package bigdata.spark.study.ThreadLocalTest;public class Bank {ThreadLocal<Integer> t = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return 100;}};public int get(){return t.get();}public void set(){t.set(t.get()+10);} }

    實現一個Runnable對象然后使用bank對象

    package bigdata.spark.study.ThreadLocalTest;public class Transfer implements Runnable {Bank bank;public Transfer(Bank bank) {this.bank = bank;}@Overridepublic void run() {for (int i =0 ;i < 10;i++){bank.set();System.out.println(Thread.currentThread()+" : " +bank.get());}} }

    定義兩個線程t1和t2,運行之后查看結果:

    package bigdata.spark.study.ThreadLocalTest;public class Test {public static void main(String[] args) {Bank bank = new Bank();Transfer t = new Transfer(bank);Thread t1 = new Thread(t);t1.start();Thread t2 = new Thread(t);t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(bank.get());} }

    查看輸出結果就會發現,發現主線程,線程t1,線程t2之間相互不影響~

    Thread[Thread-0,5,main] : 110 Thread[Thread-0,5,main] : 120 Thread[Thread-0,5,main] : 130 Thread[Thread-0,5,main] : 140 Thread[Thread-0,5,main] : 150 Thread[Thread-0,5,main] : 160 Thread[Thread-0,5,main] : 170 Thread[Thread-0,5,main] : 180 Thread[Thread-0,5,main] : 190 Thread[Thread-0,5,main] : 200 Thread[Thread-1,5,main] : 110 Thread[Thread-1,5,main] : 120 Thread[Thread-1,5,main] : 130 Thread[Thread-1,5,main] : 140 Thread[Thread-1,5,main] : 150 Thread[Thread-1,5,main] : 160 Thread[Thread-1,5,main] : 170 Thread[Thread-1,5,main] : 180 Thread[Thread-1,5,main] : 190 Thread[Thread-1,5,main] : 200 100

    3. 底層源碼

    每個線程Thread內部都會有ThreadLocal.ThreadLocalMap對象,該對象是一個自定義的map,key是弱引用包裝的ThreadLocal類型,value就是我們的值。

    初始值

    Threadlocal直接在構造的時候設置初始值。主要是要實現其initialValue方法:

    new ThreadLocal<Integer>(){@Overrideprotected IntegerinitialValue() {return 100;} };

    追蹤一下該方法,會發現其僅僅被一個私有方法調用了

    private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value; }

    解讀一下setInitialValue私有方法

  • 首先調用initialVaule方法,獲取初始值。
  • 然后獲取當前線程對象的引用。
  • 通過線程對象引用獲取ThreadLocal.ThreadLocalMap對象 map。
  • map對象不為空,就將當前threadlocal弱引用作為key,初始值為value完成初始化。
  • Map對象為空,調用createMap方法,并完成初始化。
    void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • 讀到這可能會很好奇,為啥只是被私有方法調用,我們又無權調用該私有方法,如何實現初始化呢?也是很簡單的在我們第一次調用get的時候,會調用該私有初始化方法,來真正完成初始化。

    Get方法

    具體代碼如下:

    public T get() {Thread t = Thread.currentThread();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(); }

    我們來解讀一下get方法,此處就真正暴露ThreadLocal的真實面目了。

  • 獲取當前線程對象,t
  • 通過getMap(t)方法來獲取t內部的ThreadLocal.ThreadLocalMap對象。
  • 然后判斷ThreadLocalMap對象是否為空,不為空就可以通過當前Threadlocal對象獲取對應的value值,存在返回,不存在跳過。
  • 假如map為空或者當前threadlocal對象對應的value為空,那么就調用初始化方法setInitialValue初始化并返回初始值。
  • Set

    接下來解讀一下threadlocal變量的set方法。Set的方法源碼如下:

    public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); }

    1. 獲取當前線程對象 t

    2. 通過getMap(t)方法來獲取t內部的ThreadLocal.ThreadLocalMap對象 map。

    3. map不為空,當前threadlocal對象作為key(弱引用),要設置的value作為value完成值的設置。

    4. 假如map為空,就調用createMap方法,給當前線程創建一個ThreadlocalMap

    void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue); }

    remove方法

    threadlocal的remove方法主要作用是刪除當前threadlocal對應的鍵值對。

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

    4. 內存泄漏

    根據前面對threadlocal的整理,其實可以畫出來一個結構圖:

    對value的引用線路有兩條:

  • threadlocalref 是ThreadLocal強引用,key是ThreadLocal變量的弱引用。由于key是弱引用,當ThreadLocalRef因不用而釋放掉的時候,ThreadLocal對象就會被回收,由于是key到threadLocal對象為弱引用,一旦進行垃圾回收key就會被回收而相應位置變為null,當然value依然存在。
  • 通過當前線程的引用可以獲取當前線程對象,當前線程對象就可以獲取到ThreadLocalMap,那么只要當前線程一直存在,ThreadLocalMap對象就會一直存在。
  • 由于ThreadlocalMap存活時間和線程一樣,比如我們采用的是常駐線程池,使用線程過程中沒有清空ThreadLocalMap,也沒有調用threadlocal的remove方法,就將線程放回線程池,雖然ThreadLocal的強引用ThreadLocalRef被清除,弱引用key在GC的時候也會被設置為null,但是對于value值還存在一條強引用鏈條:

    currentThreadRef-àcurrentThread-àThreadLocalMap-àEntry(value),所以value并沒有釋放,就造成了內存泄漏了。

    那這時候你或許會問為啥ThreadLocalMap存儲value的時候不采用弱引用呢?這樣不就可以避免內存泄漏了么?value是弱引用是不行的,原因很簡單:我們存儲的對象除了ThreadLocalMap的Value就沒有其他的引用了,value一但是對象的弱引用,GC的時候被回收,對象就無法訪問了,這顯然不是我們想要的。

    5. 避免內存泄漏

    為避免內存泄漏最好在使用完ThreadLocal之后調用其remove方法,將數據清除掉。

    當然,對于Java8 ThreadLocalMap 的 set 方法通過調用 replaceStaleEntry 方法回收鍵為 null 的 Entry 對象的值(即為具體實例)以及 Entry 對象本身從而防止內存泄漏

    get方法會間接調用expungeStaleEntry 方法將鍵和值為 null 的 Entry 設置為 null 從而使得該 Entry 可被回收

    總結

    以上是生活随笔為你收集整理的弱引用使用场景桌面_面试|再次讲解Threadlocal使用及其内存溢出的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。