自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

了解ThreadLocal,這一篇文章就夠了

原創(chuàng) 精選
開發(fā) 前端
很多 Java 開發(fā)一般都是做中臺(tái)較多,并發(fā)編程使用的不多。因此,對 ThreadLocal 不太熟悉,所以筆者這里想讓大家了解它,知道它是用來干什么的。

作者 | 蔡柱梁

審校 | 重樓

一、前言

很多 Java 開發(fā)一般都是做中臺(tái)較多,并發(fā)編程使用的不多。因此,對 ThreadLocal 不太熟悉,所以筆者這里想讓大家了解它,知道它是用來干什么的。

二、ThreadLocal 是用來干什么的

ThreadLocal 是 Java 中一種線程封閉技術(shù),它提供了一種線程本地變量的機(jī)制,使得每個(gè)線程都擁有一個(gè)獨(dú)立的變量副本,這樣可以避免多個(gè)線程訪問同一個(gè)變量時(shí)產(chǎn)生的并發(fā)問題。

ThreadLocal 在工作中還是蠻常用的,筆者使用到的一些場景如下:

  1. 使用 zk 實(shí)現(xiàn)選舉,采用單例 zkClient,但是對于里面一些全局變量就會(huì)存在線程安全問題,這時(shí)會(huì)希望這些特定的全局變量可以跟線程綁定。
  2. 項(xiàng)目UUC(統(tǒng)一認(rèn)證中心),不同的用戶登錄,系統(tǒng)是如何確保當(dāng)前用戶的信息不會(huì)被張冠李戴的呢?其實(shí)都是通過 ThreadLocal 實(shí)現(xiàn)的(不過在 UUC 中,筆者使用的是 InheritableThreadLocal,這個(gè)會(huì)有點(diǎn)區(qū)別)。
  3. 參數(shù)傳遞,比如流水生成的方法里面的重試機(jī)制,假設(shè)限制重試 5 次,生成流水號(hào)的方法內(nèi)部很多地方都可能失敗需要重試(并發(fā)沖突或者 db 異常),最傳統(tǒng)的方式就是將重試的次數(shù)傳遞。這種方式不夠優(yōu)雅,我們可以使用 ThreadLocal 來實(shí)現(xiàn)傳遞。

總的來說,當(dāng)你需要和線程綁定的變量時(shí),就可以考慮使用 ThreadLocal 啦!

至于線程安全問題,大家不妨想想我們平常說線程安全問題都是出現(xiàn)在什么場景?同一時(shí)間有兩個(gè)或兩個(gè)以上的線程對同一個(gè)變量進(jìn)行修改,才有可能出現(xiàn)線程安全問題。但是使用 ThreadLocal,每個(gè)線程是獨(dú)享自己的變量副本的,哪里還有線程安全問題呢?

三、ThreadLocal 如何使用

這個(gè)上網(wǎng)一搜一大堆,筆者就說下注意事項(xiàng)好了,用完后一定要釋放,避免內(nèi)存泄漏,提供幾個(gè)點(diǎn)給大家參考:

  1. 及時(shí)清理
  1. 確保在線程結(jié)束時(shí),及時(shí)清理 ThreadLocal 中存儲(chǔ)的數(shù)據(jù)??梢酝ㄟ^在使用完 ThreadLocal 后調(diào)用 remove() 方法來清理對應(yīng)的數(shù)據(jù)。例如,可以使用 ThreadLocal.remove() 或在 finally 塊中進(jìn)行清理操作。
  1. 使用弱引用(WeakReference)
  1. 可以使用 ThreadLocal 的變體,如 InheritableThreadLocal 或 WeakThreadLocal,它們使用了弱引用來存儲(chǔ)數(shù)據(jù)。這樣,在沒有其他強(qiáng)引用指向被存儲(chǔ)的對象時(shí),垃圾回收器可以自動(dòng)清理該對象,避免內(nèi)存泄漏。
  1. 避免長時(shí)間存儲(chǔ)大量數(shù)據(jù)
  1. 盡量避免在 ThreadLocal 中存儲(chǔ)大量數(shù)據(jù),特別是對于長時(shí)間運(yùn)行的線程。因?yàn)?ThreadLocal 的值在線程的整個(gè)生命周期中都存在,如果存儲(chǔ)大量數(shù)據(jù),可能會(huì)導(dǎo)致內(nèi)存占用過高。
  1. 及時(shí)釋放資源
  1. 如果你在 ThreadLocal 中存儲(chǔ)了需要手動(dòng)釋放的資源,確保在不再需要時(shí)及時(shí)釋放資源??梢酝ㄟ^在使用完資源后顯式地調(diào)用資源的釋放方法或使用 try-with-resources 語句來實(shí)現(xiàn)。
  1. 防止線程池中的內(nèi)存泄漏
  2. 當(dāng)使用線程池時(shí),要特別小心使用 ThreadLocal。確保在任務(wù)完成后清理 ThreadLocal 中的數(shù)據(jù),以避免線程重用時(shí)的數(shù)據(jù)干擾和潛在的內(nèi)存泄漏問題??梢栽谌蝿?wù)的開始和結(jié)束處使用 ThreadLocal 進(jìn)行數(shù)據(jù)綁定和解綁。

總之,要正確使用 ThreadLocal 并避免內(nèi)存泄漏問題,需要注意適時(shí)清理、使用弱引用、避免存儲(chǔ)過多數(shù)據(jù)、及時(shí)釋放資源,并在使用線程池時(shí)特別小心。

四、ThreadLocal 的實(shí)現(xiàn)原理

下面是一個(gè)簡單的示例代碼

public class ThreadLocalExample {
 private static final ThreadLocal<Object> threadLocal = new ThreadLocal<>();

 public static void main(String[] args) {
 Thread workerThread = new Thread(() -> {
 try {
 // 在線程中設(shè)置ThreadLocal值
 threadLocal.set(new Object());

 // 執(zhí)行業(yè)務(wù)邏輯
 // ... 

 } finally {
 // 在線程結(jié)束時(shí)清理ThreadLocal值
 threadLocal.remove();
 }
 });

 workerThread.start();
 // 等待線程結(jié)束
 try {
 workerThread.join();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
}

在示例代碼中,線程 workerThread 和 ThreadLocal 實(shí)例是一個(gè)怎樣的關(guān)系呢?set 方法和 remove 方法都做了什么呢?為什么會(huì)有內(nèi)存泄漏的情況呢?我們帶著疑問一起往下看。

4.1 java.lang.ThreadLocal#set

我們直接從源碼開始分析 ThreadLocal。


public void set(T value) {
 // 獲取當(dāng)前線程
 Thread t = Thread.currentThread();
 // 通過當(dāng)前線程獲取ThreadLocalMap 
 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);
 }

 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
 table = new Entry[INITIAL_CAPACITY];
 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
 table[i] = new Entry(firstKey, firstValue);
 size = 1;
 setThreshold(INITIAL_CAPACITY);
 }

 static class Entry extends WeakReference<ThreadLocal<?>> {
 /** The value associated with this ThreadLocal. */
 Object value;

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

結(jié)合示例代碼來看,這里是當(dāng)前線程A在 main 方法中通過 threadLocal 實(shí)例調(diào)用 threadLocal.set 方法,而 set 方法會(huì)給當(dāng)前線程創(chuàng)建一個(gè) ThreadLocalMap(如果沒有的話),并使用 threadLocal 實(shí)例作為 key。

它們的關(guān)系如下圖

4.2 內(nèi)存泄漏問題

這里應(yīng)該分兩種情況看:無線程復(fù)用和有線程復(fù)用。

  1. 無線程復(fù)用
    當(dāng) workerThread 結(jié)束后,沒有強(qiáng)引用的 ThreadLocalMap 自然而然也會(huì)被垃圾回收器回收,不會(huì)出現(xiàn)內(nèi)存泄漏。
  2. 有線程復(fù)用
    這里也要分開看,有釋放和無釋放的情況。如果發(fā)生內(nèi)存泄漏,當(dāng)然就是我們沒有釋放導(dǎo)致的(釋放可以通過調(diào)用 set、get、remove方法釋放)。當(dāng)我們使用線程池,線程會(huì)被復(fù)用時(shí),ThreadLocalMap 的生命周期與它綁定的線程是一樣的,所以不會(huì)被回收。如果這時(shí)發(fā)生了 gc,那么 Entry 的 key 是弱引用,key 會(huì)變成 null,而 value 將繼續(xù)存活。如果該線程一直不調(diào)用 set/get/remove 方法,那么 value 一直得不到釋放,就會(huì)發(fā)生內(nèi)存泄漏的現(xiàn)象。

那為什么使用 set/get/remove 可以避免內(nèi)存泄漏呢?因?yàn)?set/get 在根據(jù)當(dāng)前線程找到對應(yīng) Entry 元素后(這里是剛好是碰到了 key==null 的 entry[i],碰不到是不會(huì)順手釋放舊 value 的。因此,最好還是使用完后調(diào)用 remove 釋放),發(fā)現(xiàn) key == null,就會(huì)調(diào)用java.lang.ThreadLocal.ThreadLocalMap#expungeStaleEntry 釋放引用,所以就不會(huì)發(fā)生內(nèi)存泄漏了。這里就不再展示源碼了,有興趣的可以自己去看下。

五、哈希沖突問題

上面看到 ThreadLocalMap 使用了 Hash,是不是馬上就想到了哈希沖突呢?HashMap 遇到哈希沖突,在 key 不相同的情況下,會(huì)使用鏈表解決。但是 ThreadLocalMap 的 Entry 沒有 next 指針,因此它明顯不會(huì)采用鏈表,那么它是如何解決哈希沖突的呢?

請看 java.lang.ThreadLocal.ThreadLocalMap#set 源碼,筆者添加了注釋,可以看到是怎么解決哈希沖突的。

private void set(ThreadLocal<?> key, Object value) {

 // We don't use a fast path as with get() because it is at 
 // least as common to use set() to create new entries as 
 // it is to replace existing ones, in which case, a fast 
 // path would fail more often than not. 

 Entry[] tab = table;
 int len = tab.length;
 int i = key.threadLocalHashCode & (len-1);

 for (Entry e = tab[i];
 e != null;
 // 存在哈希沖突的話,會(huì)往下走,如果超過數(shù)組長度,就會(huì)回到0 
 e = tab[i = nextIndex(i, len)]) {
 ThreadLocal<?> k = e.get();

 if (k == key) {
 // 找到存儲(chǔ)自己的entry,更新value 
 e.value = value;
 return;
 }

 if (k == null) {
 // 因?yàn)?gc 導(dǎo)致 key 被回收了,這個(gè) Entry 會(huì)被新的 Entry 取代(新的Entry的key和value就是這里的傳參),舊的會(huì)被釋放
 replaceStaleEntry(key, value, i);
 return;
 }
 }

 tab[i] = new Entry(key, value);
 int sz = ++size;
 if (!cleanSomeSlots(i, sz) && sz >= threshold)
 rehash();
 }

總結(jié)

到這里相信大家對 ThreadLocal 都有一定的了解。有什么想交流可以留言或私信筆者。

作者介紹

蔡柱梁,51CTO社區(qū)編輯,從事Java后端開發(fā)8年,做過傳統(tǒng)項(xiàng)目廣電BOSS系統(tǒng),后投身互聯(lián)網(wǎng)電商,負(fù)責(zé)過訂單,TMS,中間件等。

責(zé)任編輯:華軒 來源: 51CTO
相關(guān)推薦

2018-08-23 16:22:40

2020-08-05 16:09:52

javascript壓縮圖片前端

2023-09-05 07:55:56

Python網(wǎng)絡(luò)爬蟲

2018-04-27 15:33:59

Python裝飾器

2020-08-03 10:00:11

前端登錄服務(wù)器

2023-04-24 08:00:00

ES集群容器

2020-08-17 09:25:51

Docker容器技術(shù)

2020-10-09 08:15:11

JsBridge

2021-09-27 14:50:11

Python代碼

2022-05-26 06:05:16

MySQL數(shù)據(jù)庫

2023-02-10 09:04:27

2020-02-18 16:20:03

Redis ANSI C語言日志型

2022-06-20 09:01:23

Git插件項(xiàng)目

2020-05-14 16:35:21

Kubernetes網(wǎng)絡(luò)策略DNS

2019-08-13 15:36:57

限流算法令牌桶

2022-08-01 11:33:09

用戶分析標(biāo)簽策略

2021-04-08 07:37:39

隊(duì)列數(shù)據(jù)結(jié)構(gòu)算法

2023-09-11 08:13:03

分布式跟蹤工具

2023-05-12 08:19:12

Netty程序框架

2021-06-30 00:20:12

Hangfire.NET平臺(tái)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)