www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > 架構師社區(qū)
[導讀]來自:碼匠筆記 ThreadLocal 對于大家并不陌生,每個人應該多少都用過,或者接觸過,那么你真的了解她嗎?我也是今天才揭開了她的神秘面紗。 看完這篇文章你將 GET 如下知識點: 什么是 ThreadLocal? ThreadLocal 真的會導致內(nèi)存溢出嗎? ThreadLocal 源碼淺

來自:碼匠筆記


ThreadLocal對于大家并不陌生,每個人應該多少都用過,或者接觸過,那么你真的了解她嗎?我也是今天才揭開了她的神秘面紗。

看完這篇文章你將 GET 如下知識點:

  1. 什么是 ThreadLocal?
  2. ThreadLocal 真的會導致內(nèi)存溢出嗎?
  3. ThreadLocal 源碼淺析
  4. ThreadLocal 最佳實踐
  5. ThreadLocal.remove 解決的到底是什么問題?

ThreadLocal 是什么?

ThreadLocal字面意思是線程本地變量,那么什么是線程本地變量呢?他解決了什么問題?先看下面這個例子

public class ThreadLocalTest { public static void main(String[] args) {
        Task task = new Task(); for (int i = 0; i < 3; i++) { new Thread(() -> System.out.println(Thread.currentThread().getName() + " : " + task.calc(10))).start();
        }
    } static class Task { private int value; public int calc(int i) {
            value += i; return value;
        }
    }
}

內(nèi)容很簡單,啟動 3 個線程,分別調(diào)用 calc 方法,然后打印線程名字和計算內(nèi)容,輸出如下:

Thread-0 : 10
Thread-1 : 20
Thread-2 : 30

結果不難分析,因為這么 3 個線程共用一個 Task對象,所以 value 內(nèi)容會累加,那么結果是不是不是你預期呢?那么我們再看一個例子

public class ThreadLocalTest2 { public static void main(String[] args) {
        ThreadLocalTest2.Task task = new ThreadLocalTest2.Task(); for (int i = 0; i < 3; i++) { new Thread(() -> System.out.println(Thread.currentThread().getName() + " : " + task.calc(10))).start();
        }
    } static class Task {
        ThreadLocalvalue; public int calc(int i) {
            value = new ThreadLocal();
            value.set((value.get() == null ? 0 : value.get()) + i); return value.get();
        }
    }
}

運行結果如下

Thread-0 : 10
Thread-1 : 10
Thread-2 : 10

這次結果就對了吧,把 value 修改成了 ThreadLocal,然后每個線程就不會互相影響內(nèi)容了,那么為什么他可以做到呢?這就是  ThreadLocal的意義所在,他解決的就是線程私有變量,多線程不互相影響。我們?nèi)ピ创a一看究竟

ThreadLocal 源碼賞析

看源碼最簡單粗暴的方式就是從入口進行,我們直接看 ThreadLocal.set方法,她直接獲取了當前線程,然后調(diào)用了 getMap(t),也就是當前線程的 threadLocals變量,如果沒有直接調(diào)用 createMap創(chuàng)建,然后返回,那么看到這里我們就知道了,ThreadLocal就是一個工具類,讓我們可以把內(nèi)容通過k-v的方式設置在當前線程上面(里面實際是使用 ThreadLocalMap進行存儲,秒看一下代碼和 HashMap 原理非常相似),既然存儲在當前線程上面那么當然不會有線程安全問題了,這就是線程本地變量的內(nèi)容嘍。

當然我們要尤為注意,key 是 this 也就是當前的 ThreadLocal對象,記住這點下文要說呢。

public void set(T value) {
      Thread t = Thread.currentThread();
      ThreadLocalMap map = getMap(t); if (map != null)
          map.set(this, value); else createMap(t, value);
  }
ThreadLocalMap getMap(Thread t) { return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
   t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal 會內(nèi)存溢出嗎?

不過還沒有結束,大家最愛談了的就是 ThreadLocal 的內(nèi)存溢出問題,那么她真的會內(nèi)存溢出么?

我們再看一個例子,例子和剛才不同的地方是只使用了一個線程(也就是 Main 線程)循環(huán)運行示例,每次創(chuàng)建新的 Task 對象,我們可想而知,這樣每次創(chuàng)建不同的 Task,只要線程不結束,會不停的往當前線程的 threadLocals里面 set 內(nèi)容,因為每次都是新 Task ,自然 ThreadLocal也是新的,那么如果循環(huán)足夠大,并且線程一直存在,肯定會內(nèi)存溢出呢呀?。?!我們自己動手試試才知道啊。

下面的例子中,在 i == 80 的時候做了一次強制 GC,我們直接 DEBUG 看下效果。

public class ThreadLocalTest3 { public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Task().calc(10); if (i == 80) {
                System.gc();
            }
        }
    } static class Task {
        ThreadLocalvalue; public int calc(int i) {
            value = new ThreadLocal();
            value.set((value.get() == null ? 0 : value.get()) + i); return value.get();
        }
    }
}

在 for 循環(huán)行的左側點擊 debug,然后點擊右鍵,這樣 DEBUG 會停留在循環(huán)變量 i 等于 79 和 81 的地方,循環(huán) 100 次是為了更好的查看效果。好了我們可以直接觀察一下 i == 80 前后的運行情況了

i == 79 || i == 81

那么開始我的表演,DEBUG 分別停在了 79 和 81 的位置上面,我們直接運行一下當前線程的內(nèi)容獲取到 threadLocals的內(nèi)容

Thread.currentThread().threadLocals

可以看到里面的 ThreadLocalMap 的 size 分別是 83 和 4,這說明了什么?GC的時候把 83-4 = 79 個 ThreadLocalMap的內(nèi)容回收了?


好吧,那我們繼續(xù)看下代碼吧

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table; int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked")
                    ThreadLocalkey = (ThreadLocal) e.get(); if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value;

            Entry(ThreadLocal k, Object v) { super(k);
                value = v;
            }
        }

原來 ThreadLocal的 ThreadLocalMap里面存的每一個 Entry 是一個 WeakReference,WeakReference會在 GC 的時候進行回收,回收的其實是 key,也就是弱引用的 referent, 然后  ThreadLocal會在 set 和 get 的時候對 key 為空的 value 進行刪除,所以這樣就完美解決了當前線程生命周期不結束的時候,不同的 ThreadLocal不停的追加到當前線程上面,導致內(nèi)存溢出。

等等,那我自己寫個程序,遇到 GC 不是就獲取不到 ThreadLocal對象了嗎?不是的,因為一個對象只有僅僅被 WeakReference引用才會被回收。

哎,如果 work1 的引用不在了,并且 Entry 對 ThreadLocal 的引用是弱引用才會回收,是不是很巧妙的解決了這個問題?


所以 WeakReference解決的就是內(nèi)存溢出問題,如果持有 ThreadLocal 對象被回收了,內(nèi)存自然會被回收,如果 ThreadLocal 的對象一直存在不被回收,并不能稱之為內(nèi)存溢出。

ThreadLocal 最佳實踐

千呼萬喚始出來,因為 ThreadLocal這個特性,深受各種框架喜歡,比如 MyBatis,Spring 大量的使用的 ThreadLocal,下面是用一個最常用的案例說明一下,首先我有一個攔截器,每次請求來,使用當前的 sl 的內(nèi)容 + 10,我是為了模擬效果,通常這個做法是用于傳遞當前登錄態(tài),以便一次請求在任何地方都可以輕松的獲取到登錄態(tài)。

public class SessionInterceptor implements HandlerInterceptor { public static ThreadLocalsl = new ThreadLocal(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        Integer value = sl.get(); if (value != null) {
            sl.set(value + 10);
        } else {
            sl.set(10);
        } return true;
    } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {

    } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {

    }
}

然后我在 controller 里面獲取 ThreadLocal里面的內(nèi)容,并打印當前線程的名稱和內(nèi)容

@RestController public class IndexController { @RequestMapping("/") public Integer test() {
        System.out.println(Thread.currentThread().getName() + " : " + SessionInterceptor.sl.get()); return SessionInterceptor.sl.get();
    }
}

接下來我們啟動服務,運行我編寫好的 Spring Boot Application

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class MainBootstrap { public static void main(String[] args) {
        SpringApplication.run(MainBootstrap.class, args);
    }
}

瀏覽器訪問 https://localhost:8080,瘋狂的刷新瀏覽器,控制臺打印的效果如下

http-nio-8080-exec-1 : 10
http-nio-8080-exec-3 : 10
http-nio-8080-exec-4 : 10
http-nio-8080-exec-1 : 20
http-nio-8080-exec-2 : 10
http-nio-8080-exec-3 : 20
http-nio-8080-exec-4 : 20
http-nio-8080-exec-1 : 30
http-nio-8080-exec-2 : 20
http-nio-8080-exec-3 : 30
http-nio-8080-exec-4 : 30

呀,和我想象的不一樣啊,我這可是瀏覽器的請求,不應該是每個請求一個線程,使用自己的 ThreadLocal 嗎,怎么值也累加了?

別慌問題出現(xiàn)在這里,在池化技術流行的年代,自然 Tomcat 也用了池化基礎,其實每個請求過來,是直接在 Tomcat 的線程池里面獲取一個線程,然后運行,所以一個請求結束如果 ThreadLocal 的內(nèi)容不重置,就會影響其他請求,想象如果你這個地方是做的用戶登錄的綁定,那么豈不是資源亂套了?

那么怎么解決呢?還記得剛才的 SessionInterceptor 類么,直接在里面的 afterCompletion添加 sl.remove()即可,意思是在請求結束的時候,把當前線程的私有變量刪除,這樣就不影響其他線程了。

網(wǎng)上的一些說這個操作是為了更好的 GC 回收沒用的實例,如果不設置也會自動回收,其實是不對的。為了讓上下文都可以獲取到 ThreadLocal 的內(nèi)容,所以比如是靜態(tài)的 ThreadLocal 所以持有的引用一直存在,并不會被回收,所以其實是在恢復線程的狀態(tài),不影響其他請求。

@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        sl.remove();
    }

修改以后我們重新狂刷瀏覽器,是不是問題就解決了呢?好的如果你有任何關于 ThreadLocal 的問題歡迎給我留言其他討論,如果有不對的地方也歡迎指正。對了所有文章中的代碼,都可以在訂閱號后臺回復 “ThreadLocal” 獲取。

特別推薦一個分享架構+算法的優(yōu)質內(nèi)容,還沒關注的小伙伴,可以長按關注一下:

用了三年 ThreadLocal 今天才弄明白其中的道理

長按訂閱更多精彩▼

用了三年 ThreadLocal 今天才弄明白其中的道理

如有收獲,點個在看,誠摯感謝

免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉