日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

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

來自:碼匠筆記


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

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

  1. 什么是 ThreadLocal?
  2. ThreadLocal 真的會導致內存溢出嗎?
  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;
        }
    }
}

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

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

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

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,然后每個線程就不會互相影響內容了,那么為什么他可以做到呢?這就是  ThreadLocal的意義所在,他解決的就是線程私有變量,多線程不互相影響。我們去源碼一看究竟

ThreadLocal 源碼賞析

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

當然我們要尤為注意,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 會內存溢出嗎?

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

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

下面的例子中,在 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 的位置上面,我們直接運行一下當前線程的內容獲取到 threadLocals的內容

Thread.currentThread().threadLocals

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


好吧,那我們繼續(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不停的追加到當前線程上面,導致內存溢出。

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

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


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

ThreadLocal 最佳實踐

千呼萬喚始出來,因為 ThreadLocal這個特性,深受各種框架喜歡,比如 MyBatis,Spring 大量的使用的 ThreadLocal,下面是用一個最常用的案例說明一下,首先我有一個攔截器,每次請求來,使用當前的 sl 的內容 + 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里面的內容,并打印當前線程的名稱和內容

@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 嗎,怎么值也累加了?

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

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

網上的一些說這個操作是為了更好的 GC 回收沒用的實例,如果不設置也會自動回收,其實是不對的。為了讓上下文都可以獲取到 ThreadLocal 的內容,所以比如是靜態(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)質內容,還沒關注的小伙伴,可以長按關注一下:

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

長按訂閱更多精彩▼

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

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

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

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

LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: 驅動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設備,其驅動電源的性能直接關系到整個系統的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅動電源設計中至關重要的兩個環(huán)節(jié),集成化方案的設計成為提升電機驅動性能的關鍵。

關鍵字: 工業(yè)電機 驅動電源

LED 驅動電源作為 LED 照明系統的 “心臟”,其穩(wěn)定性直接決定了整個照明設備的使用壽命。然而,在實際應用中,LED 驅動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設計、生...

關鍵字: 驅動電源 照明系統 散熱

根據LED驅動電源的公式,電感內電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關鍵字: LED 設計 驅動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產業(yè)的重要發(fā)展方向。電動汽車的核心技術之一是電機驅動控制系統,而絕緣柵雙極型晶體管(IGBT)作為電機驅動系統中的關鍵元件,其性能直接影響到電動汽車的動力性能和...

關鍵字: 電動汽車 新能源 驅動電源

在現代城市建設中,街道及停車場照明作為基礎設施的重要組成部分,其質量和效率直接關系到城市的公共安全、居民生活質量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統光源,成為大功率區(qū)域...

關鍵字: 發(fā)光二極管 驅動電源 LED

LED通用照明設計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數校正(PFC)、空間受限和可靠性等。

關鍵字: LED 驅動電源 功率因數校正

在LED照明技術日益普及的今天,LED驅動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設備造成不利影響,甚至引發(fā)系統故障。因此,采取有效的硬件措施來解決L...

關鍵字: LED照明技術 電磁干擾 驅動電源

開關電源具有效率高的特性,而且開關電源的變壓器體積比串聯穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現在的LED驅動電源

關鍵字: LED 驅動電源 開關電源

LED驅動電源是把電源供應轉換為特定的電壓電流以驅動LED發(fā)光的電壓轉換器,通常情況下:LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: LED 隧道燈 驅動電源
關閉