激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - ThreadLocal常用方法、使用場(chǎng)景及注意事項(xiàng)說(shuō)明

ThreadLocal常用方法、使用場(chǎng)景及注意事項(xiàng)說(shuō)明

2022-02-15 16:08一碼事 Java教程

這篇文章主要介紹了ThreadLocal常用方法、使用場(chǎng)景及注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

1. ThreadLocal詳解

JDK1.2版本起,Java就提供了java.lang.ThreadLocal,ThreadLocal為每個(gè)使用線程都提供獨(dú)立的變量副本,可以做到線程間的數(shù)據(jù)隔離,每個(gè)線程都可以訪問(wèn)各自內(nèi)部的副本變量。

線程上下文ThreadLocal又稱為"線程保險(xiǎn)箱",ThreadLocal能夠?qū)⒅付ǖ淖兞亢彤?dāng)前線程進(jìn)行綁定,線程之間彼此隔離,持有不同的對(duì)象實(shí)例,從而避免了數(shù)據(jù)資源的競(jìng)爭(zhēng)。

 

2. ThreadLocal的使用場(chǎng)景

  • 在進(jìn)行對(duì)象跨層傳遞的時(shí)候,可以考慮ThreadLocal,避免方法多次傳遞,打破層次間的約束。
  • 線程間數(shù)據(jù)隔離。
  • 進(jìn)行事務(wù)操作,用于儲(chǔ)存線程事務(wù)信息。

注意:

ThreadLocal并不是解決多線程下共享資源的一種技術(shù),一般情況下,每一個(gè)線程的ThreadLocal存儲(chǔ)的都是一個(gè)全新的對(duì)象(通過(guò)new關(guān)鍵字創(chuàng)建),如果多線程的ThreadLocal存儲(chǔ)了一個(gè)對(duì)象引用,那么就會(huì)面臨資源競(jìng)爭(zhēng),數(shù)據(jù)不一致等并發(fā)問(wèn)題。

 

3.常用方法源碼解析

3.1 initialValue方法

protected T initialValue() {
      return null;
}

此方法為ThreadLocal保存的數(shù)據(jù)類型指定的一個(gè)初始化值,在ThreadLocal中默認(rèn)返回null。但可以重寫initialValue()方法進(jìn)行數(shù)據(jù)初始化。

如果使用的是Java8提供的Supplier函數(shù)接口更加簡(jiǎn)化:

// withInitial()實(shí)際是創(chuàng)建了一個(gè)ThreadLocal的子類SuppliedThreadLocal,重寫initialValue()
ThreadLocal<Object> threadLocal = ThreadLocal.withInitial(Object::new);

3.2 set(T value)方法

主要存儲(chǔ)指定數(shù)據(jù)。

public void set(T value) {
  // 獲取當(dāng)前線程Thread.currentThread() 
  Thread t = Thread.currentThread();
  // 根據(jù)當(dāng)前線程獲取與之關(guān)聯(lián)的ThreadLocalMap數(shù)據(jù)結(jié)構(gòu)
  ThreadLocalMap map = getMap(t);
  if (map != null)
      // 核心方法。set 遍歷整個(gè)Entry的過(guò)程,后面有詳解
      map.set(this, value);
  else {
      // 調(diào)用createMap(),創(chuàng)建ThreadLocalMap,key為當(dāng)前ThreadLocal實(shí)例,存入數(shù)據(jù)為當(dāng)前value。
      // ThreadLocal會(huì)創(chuàng)建一個(gè)默認(rèn)長(zhǎng)度為16Entry節(jié)點(diǎn),并將k-v放入i位置(i位置計(jì)算方式和hashmap相似,
      // 當(dāng)前線程的hashCode&(entry默認(rèn)長(zhǎng)度-1)),并設(shè)置閾值(默認(rèn)為0)為Entry默認(rèn)長(zhǎng)度的2/3。
      createMap(t, value);
  }
}
// set 遍歷整個(gè)Entry的過(guò)程
private void set(ThreadLocal<?> key, Object value) {
  // 獲取所有的Entry
  Entry[] tab = table;
  int len = tab.length;
  // 根據(jù)ThreadLocal對(duì)象,計(jì)算角標(biāo)位置
  int i = key.threadLocalHashCode & (len-1);
// 循環(huán)查找
  for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
      ThreadLocal<?> k = e.get();
// 找到相同的就直接覆蓋,直接返回。
      if (k == key) {
          e.value = value;
          return;
      }
// 如果ThreadLocal為null,直接驅(qū)出并使用新數(shù)據(jù)(Value)占居原來(lái)位置,
// 這個(gè)過(guò)程主要是防止內(nèi)存泄漏。
      if (k == null) {
          // 驅(qū)除ThreadLocal為null的Entry,并放入Value,這也是內(nèi)存泄漏的重點(diǎn)地區(qū)
          replaceStaleEntry(key, value, i);
          return;
      }
  }
// entry都為null,創(chuàng)建新的entry,已ThreadLocal為key,將存放數(shù)據(jù)為Value。
  tab[i] = new Entry(key, value);
  int sz = ++size;
  // ThreadLoaclMapde的當(dāng)前數(shù)據(jù)元素的個(gè)數(shù)和閾值比較,再次進(jìn)行key為null的清理工作。
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
      // 整理Entry,當(dāng)Entry中的ThreadLocal對(duì)象為null時(shí),通過(guò)重新計(jì)算角標(biāo)位來(lái)清理
      // 以前ThreadLocal。如果Entry數(shù)量大于3/4容量進(jìn)行擴(kuò)容
      rehash();
}

3.3 get方法

get()用于返回當(dāng)前線程ThreadLocal中數(shù)據(jù)備份,當(dāng)前線程的數(shù)據(jù)都存在一個(gè)ThreadLocalMap的數(shù)據(jù)結(jié)構(gòu)中。

public T get() {
  Thread t = Thread.currentThread();
  // 獲得ThreadLocalMap對(duì)象map,ThreadLocalMap是和當(dāng)前Thread關(guān)聯(lián)的,
  ThreadLocalMap map = getMap(t);
  if (map != null) {
      // 存入ThreadLocal中的數(shù)據(jù)實(shí)際上是存儲(chǔ)在ThreadLocalMap的Entry中。
      // 而此Entry是放在一個(gè)Entry數(shù)組里面的。
      // 獲取當(dāng)前ThreadLocal對(duì)應(yīng)的entry
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) {
          // 直接返回當(dāng)前數(shù)據(jù) 
          T result = (T)e.value;
          return result;
      }
  }
  // ThreadLocalMap未初始化,首先初始化
  return setInitialValue();
}
// ThreadLocal的setInitialValue方法源碼
private T setInitialValue() {
  // 為ThreadLocalMap指定Value的初始化值
  T value = initialValue();
  Thread t = Thread.currentThread();
  // 根據(jù)本地線程Thread獲取ThreadLocalMap,一下方法與Set方法相同。
  ThreadLocalMap map = getMap(t);
  if (map != null)
      // 如果map存在,直接調(diào)用set()方法進(jìn)行賦值。
      map.set(this, value);
  else
      // map==null;創(chuàng)建ThreadLocalMap對(duì)象,并將Thread和value關(guān)聯(lián)起來(lái)
      createMap(t, value);
  return value;
}

3.4 小結(jié)

  • initialValue():初始化ThreadLocal中的value屬性值。
  • set():獲取當(dāng)前線程,根據(jù)當(dāng)前線程從ThreadLocals中獲取ThreadLocalMap數(shù)據(jù)結(jié)構(gòu),
    • 如果ThreadLocalmap的數(shù)據(jù)結(jié)構(gòu)沒創(chuàng)建,則創(chuàng)建ThreadLocalMap,key為當(dāng)前ThreadLocal實(shí)例,存入數(shù)據(jù)為當(dāng)前value。ThreadLocal會(huì)創(chuàng)建一個(gè)默認(rèn)長(zhǎng)度為16Entry節(jié)點(diǎn),并將k-v放入i位置(i位置計(jì)算方式和hashmap相似,當(dāng)前線程的hashCode&(entry默認(rèn)長(zhǎng)度-1)),并設(shè)置閾值(默認(rèn)為0)為Entry默認(rèn)長(zhǎng)度的2/3。
    • 如果ThreadLocalMap存在。就會(huì)遍歷整個(gè)Map中的Entry節(jié)點(diǎn),如果entry中的key和本線程ThreadLocal相同,將數(shù)據(jù)(value)直接覆蓋,并返回。如果ThreadLoca為null,驅(qū)除ThreadLocal為null的Entry,并放入Value,這也是內(nèi)存泄漏的重點(diǎn)地區(qū)。
  • get()
  • get()方法比較簡(jiǎn)單。就是根據(jù)Thread獲取ThreadLocalMap。通過(guò)ThreadLocal來(lái)獲得數(shù)據(jù)value。注意的是:如果ThreadLocalMap沒有創(chuàng)建,直接進(jìn)入創(chuàng)建過(guò)程。初始化ThreadLocalMap。并直接調(diào)用和set方法一樣的方法。

3.4 ThreadLocalMap數(shù)據(jù)結(jié)構(gòu)

set()還是get()方法都是避免不了和ThreadLocalMap和Entry打交道。ThreadLocalMap是一個(gè)類似于HashMap的一個(gè)數(shù)據(jù)結(jié)構(gòu)(沒有鏈表),僅僅用于存放線程存放在ThreadLocal中的數(shù)據(jù)備份,ThreadLocalMap的所有方法對(duì)外部都是不可見的。

ThreadLocalMap中用于存儲(chǔ)數(shù)據(jù)的Entry,它是一個(gè)WeakReference類型的子類,之所以設(shè)計(jì)成WeakReference是為了能夠是JVM發(fā)生gc,能夠自動(dòng)回收,防止內(nèi)存溢出現(xiàn)象。

 

4. ThreadLocal的副作用

4.1 ThreadLocal引起臟數(shù)據(jù)

線程復(fù)用會(huì)產(chǎn)生臟數(shù)據(jù)。

由于結(jié)程池會(huì)重用 Thread 對(duì)象 ,那么與 Thread 綁定的類的靜態(tài)屬性 ThreadLocal 變量也會(huì)被重用。如果在實(shí)現(xiàn)的線程 run()方法體中不顯式地調(diào)用 remove() 清理與線程相關(guān)的ThreadLocal 信息,那么如果下一個(gè)線程不調(diào)用set()設(shè)置初始值,就可能 get()到重用的線程信息,包括 ThreadLocal 所關(guān)聯(lián)的線程對(duì)象的 value 值。

// java.lang.Thread#threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
   * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

4.2 ThreadLocal引起的內(nèi)存泄漏

在上面提到ThreadLocalMap中存放的Entry是WeakReference的子類。所以在JVM觸發(fā)GC(young gc,F(xiàn)ull GC)時(shí),都會(huì)導(dǎo)致Entry的回收

在get數(shù)據(jù)的時(shí)候,增加檢查,清除已經(jīng)被回收器回收的Entry(WeakReference可以自動(dòng)回收)

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
  ThreadLocal<?> k = e.get();
...
  if (k == null)
      // 清除 key 是 null 的Entry
      expungeStaleEntry(i);
...
return null;
}
private boolean cleanSomeSlots(int i, int n) {
  boolean removed = false;
  Entry[] tab = table;
  int len = tab.length;
  do {
      i = nextIndex(i, len);
      Entry e = tab[i];
      if (e != null && e.get() == null) {
          n = len;
          removed = true;
          // 清除key==null 的Entry
          i = expungeStaleEntry(i);
      }
  } while ( (n >>>= 1) != 0);
  return removed;
}

set數(shù)據(jù)時(shí)增加檢查,刪除已經(jīng)被垃圾回收器清理的Entry,并將其移除

private boolean cleanSomeSlots(int i, int n) {
  boolean removed = false;
  Entry[] tab = table;
  int len = tab.length;
  do {
      i = nextIndex(i, len);
      Entry e = tab[i];
      if (e != null && e.get() == null) {
          n = len;
          removed = true;
          // 清除key==null 的Entry
          i = expungeStaleEntry(i);
      }
  } while ( (n >>>= 1) != 0);
  return removed;
}

基于上面三點(diǎn):ThreadLocal在一定程度上保證不會(huì)發(fā)生內(nèi)存泄漏。但是Thread類中有ThreadlocalMap的引用,導(dǎo)致對(duì)象的可達(dá)性,故不能回收。

ThreadLocal被置為null清除了。但是通過(guò)ThreadLocalMap還是被Thread類引用。導(dǎo)致該數(shù)據(jù)是可達(dá)的。所以內(nèi)存得不到釋放,除非當(dāng)前線程結(jié)束,Thread引用就會(huì)被垃圾回收器回收。如圖所示

ThreadLocal常用方法、使用場(chǎng)景及注意事項(xiàng)說(shuō)明

ThreadLocal常用方法、使用場(chǎng)景及注意事項(xiàng)說(shuō)明5

 

5. ThreadLocal內(nèi)存泄漏解決方案及remove方法源碼解析

解決ThreadLocal內(nèi)存泄漏的常用方法是:在使用完ThreadLocal之后,及時(shí)remove掉。

public void remove() {
  // 根據(jù)當(dāng)前線程,獲取ThreadLocalMap
  ThreadLocalMap m = getMap(Thread.currentThread());
  if (m != null)
      // map不為null,執(zhí)行remove操作
      m.remove(this);
}
// ThreadLocal 的remove()
private void remove(ThreadLocal<?> key) {
  // 獲取存放key-value的數(shù)組。
  Entry[] tab = table;
  int len = tab.length;
  // 根據(jù)ThreadLocal的HashCode確定唯一的角標(biāo)
  int i = key.threadLocalHashCode & (len-1);
  for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
      if (e.get() == key) {
          // 如果和本ThreadLocal相同。將引用置null。
          e.clear();
          // 實(shí)行Enty和Entry.value置null。源碼中 tab[staleSlot].value = null; tab[staleSlot] = null;
          expungeStaleEntry(i);
          return;
      }
  }
}

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。

原文鏈接:https://blog.csdn.net/qq_42742861/article/details/90649837

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 91成人免费在线视频 | 国产成年免费视频 | 欧美亚洲国产成人综合在线 | 久草在线高清 | 古装三级在线观看 | 国产精品视频中文字幕 | 欧美成人黄色小视频 | 亚洲影视在线 | 欧美中文字幕一区二区三区亚洲 | 黄色网址进入 | 一级片999 | 一级尻逼视频 | 国产精品v片在线观看不卡 成人一区二区三区在线 | 国产亚洲精品久久久久久网站 | 欧美一级视频在线观看 | 毛片三区 | 91成人免费看片 | 国产一国产一级毛片视频在线 | 欧美四级在线观看 | 色999国产| 大奶一级片 | 特级a欧美做爰片毛片 | 免费观看黄色一级视频 | 久久久久久久久日本理论电影 | av免费提供| 在线观看国产 | 成人福利视频导航 | 国产精品一区二区三区在线 | 草草免费视频 | 日韩黄色一区 | 欧美国产成人在线 | 久草视频在线资源 | 国产九九| 国产亚洲精品成人a | 日本视频免费看 | 久久精品亚洲精品国产欧美kt∨ | 久国产| 国产羞羞视频在线观看 | 欧美人一级淫片a免费播放 久久99精品久久久久久园产越南 | 免费a网| 蜜桃传媒视频麻豆第一区免费观看 |