最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中ThreadLocal的一些理解

 更新時間:2020年12月07日 09:15:08   作者:紀莫  
這篇文章主要介紹了Java中ThreadLocal的一些理解,幫助大家更好的理解和學習Java,感興趣的朋友可以了解下

前言

面試的時候被問到ThreadLocal的相關知識,沒有回答好(奶奶的,現(xiàn)在感覺問啥都能被問倒),所以我決定先解決這幾次面試中都遇到的高頻問題,把這幾個硬骨頭都能理解的透徹的說出來了,感覺最起碼不能總是一輪游。

ThreadLocal介紹

ThreadLocal是JDK1.2開始就提供的一個用來存儲線程本地變量的類。ThreadLocal中的變量是在每個線程中獨立存在的,當多個線程訪問ThreadLocal中的變量的時候,其實都是訪問的自己當前線程的內(nèi)存中的變量,從而保證的變量的線程安全。

我們一般在使用ThreadLocal的時候都是為了解決線程中存在的變量競爭問題。其實解決這類問題,通常大家也會想到使用synchronized來加鎖解決。

例如在解決SimpleDateFormat的線程安全的時候。SimpleDateFormat是非線程安全的,它里面無論的是format()方法還是parse()方法,都有使用它自己內(nèi)部的一個Calendar類的對象,format方法是設置時間,parse()方法里面是先調(diào)用Calendar的clear()方法,然后又調(diào)用了Calendar的set()方法(賦值),如果一個線程剛調(diào)用了set()進行賦值,這個時候又來了一個線程直接調(diào)用了clear()方法,那么這個parse()方法執(zhí)行的結果就會有問題的。

解決辦法一
將使用SimpleDateformat的方法加上synchronized,這樣雖然保證了線程安全,但卻降低了效率,同一時間只有一個線程能使用格式化時間的方法。

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static synchronized String formatDate(Date date){
  return simpleDateFormat.format(date);
}

解決辦法二
將SimpleDateFormat的對象,放到ThreadLocal里面,這樣每個線程中都有一個自己的格式對象的副本了?;ゲ桓蓴_,從而保證了線程安全。

private static final ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

public static String formatDate(Date date){
  return simpleDateFormatThreadLocal.get().format(date);
}

ThreadLocal的原理

我們先看一下ThreadLocal是怎么使用的。

ThreadLocal<Integer> threadLocal99 = new ThreadLocal<Integer>();
threadLocal99.set(3);
int num = threadLocal99.get();
System.out.println("數(shù)字:"+num);
threadLocal99.remove();
System.out.println("數(shù)字Empty:"+threadLocal99.get());

運行結果:

數(shù)字:3
數(shù)字Empty:null

使用起來很簡單,主要是將變量放到ThreadLocal里面,在線程執(zhí)行過程中就可以取到,當執(zhí)行完成后在remove掉就可以了,只要沒有調(diào)用remove()當前線程在執(zhí)行過程中都是可以拿到變量數(shù)據(jù)的。
因為是放到了當前執(zhí)行的線程中,所以ThreadLocal中的變量值只能當前線程來使用,從而保證的了線程安全(當前線程的子線程其實也是可以獲取到的)。

來看一下ThreadLocal的set()方法源碼

public void set(T value) {
  // 獲取當前線程
  Thread t = Thread.currentThread();
  // 獲取ThreadLocalMap
  ThreadLocal.ThreadLocalMap map = getMap(t);
  // ThreadLocalMap 對象是否為空,不為空則直接將數(shù)據(jù)放入到ThreadLocalMap中
  if (map != null)
    map.set(this, value);
  else
    createMap(t, value); // ThreadLocalMap對象為空,則先創(chuàng)建對象,再賦值。
}

我們看到變量都是存放在了ThreadLocalMap這個變量中的。那么ThreadLocalMap又是怎么來的呢?

ThreadLocalMap getMap(Thread t) {
  return t.threadLocals;
}
public class Thread implements Runnable {
	... ...
	/* ThreadLocal values pertaining to this thread. This map is maintained
   * by the ThreadLocal class. */
  ThreadLocal.ThreadLocalMap threadLocals = null;
  ... ...
}

通過上面的源碼,我們發(fā)現(xiàn)ThreadLocalMap變量是當前執(zhí)行線程中的一個變量,所以說,ThreadLocal中存放的數(shù)據(jù)其實都是放到了當前執(zhí)行線程中的一個變量里面了。也就是存儲在了當前的線程對象里了,別的線程里面是另一個線程對象了,拿不到其他線程對象中的數(shù)據(jù),所以數(shù)據(jù)自然就隔離開了。

那么ThreadLocalMap是怎么存儲數(shù)據(jù)的呢?
ThreadLocalMap 是ThreadLocal類里的一個內(nèi)部類,雖然類的名字上帶著Map但卻沒有實現(xiàn)Map接口,只是結構和Map類似而已。

ThreadLocalMap內(nèi)部其實是一個Entry數(shù)組,Entry是ThreadLocalMap中的一個內(nèi)部類,繼承自WeakReference,并將ThreadLocal類型的對象設置為了Entry的Key,以及對Key設置成弱引用。
ThreadLocalMap的內(nèi)部數(shù)據(jù)結構,就大概是這樣的key,value組成的Entry的數(shù)組集合。

和真正的Map還是有區(qū)別的,沒有鏈表了,這樣在解決key的hash沖突的時候措施肯定就和HashMap不一樣了。
一個線程中是可以創(chuàng)建多個ThreadLocal對象的,多個ThreadLocal對象就會存放多個數(shù)據(jù),那么在ThreadLocalMap中就會以數(shù)組的形式存放這些數(shù)據(jù)。
我們來看一下具體的ThreadLocalMap的set()方法的源碼

/**
 * Set the value associated with key.
 * @param key the thread local object
 * @param value the value to be 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;
  // 定位在數(shù)組中的位置
  int i = key.threadLocalHashCode & (len-1);

  for (Entry e = tab[i];
     e != null;
     e = tab[i = nextIndex(i, len)]) {
    ThreadLocal<?> k = e.get();
    // 如果當前位置不為空,并且當前位置的key和傳過來的key相等,那么就會覆蓋當前位置的數(shù)據(jù)
    if (k == key) {
      e.value = value;
      return;
    }
    // 如果當前位置為空,則初始化一個Entry對象,放到當前位置。
    if (k == null) {
      replaceStaleEntry(key, value, i);
      return;
    }
  }
  // 如果當前位置不為空,并且當前位置的key也不等于要賦值的key ,那么將去找下一個空位置,直接將數(shù)據(jù)放到下一個空位置處。
  tab[i] = new Entry(key, value);
  int sz = ++size;
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();
}

我們從set()方法中可以看到,處理邏輯有四步。

  • 第一步先根據(jù)Threadlocal對象的hashcode和數(shù)組長度做與運算獲取數(shù)據(jù)應該放在當前數(shù)組中的位置。
  • 第二步就是判斷當前位置是否為空,為空的話就直接初始化一個Entry對象,放到當前位置。
  • 第三步如果當前位置不為空,而當前位置的Entry中的key和傳過來的key一樣,那么直接覆蓋掉當前位置的數(shù)據(jù)。
  • 第四步如果當前位置不為空,并且當前位置的Entry中的key和傳過來的key
  • 也不一樣,那么就會去找下一個空位置,然后將數(shù)據(jù)存放到空位置(數(shù)組超過長度后,會執(zhí)行擴容的);

在get的時候也是類似的邏輯,先通過傳入的ThreadLocal的hashcode獲取在Entry數(shù)組中的位置,然后拿當前位置的Entry的Key和傳入的ThreadLocal對比,相等的話,直接把數(shù)據(jù)返回,如果不相等就去判斷和數(shù)組中的下一個值的key是否相等。。。

private Entry getEntry(ThreadLocal<?> key) {
  int i = key.threadLocalHashCode & (table.length - 1);
  Entry e = table[i];
  if (e != null && e.get() == key)
    return e;
  else
    return getEntryAfterMiss(key, i, e);
}
/**
 * Version of getEntry method for use when key is not found in
 * its direct hash slot.
 *
 * @param key the thread local object
 * @param i the table index for key's hash code
 * @param e the entry at table[i]
 * @return the entry associated with key, or null if no such
 */
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
  Entry[] tab = table;
  int len = tab.length;

  while (e != null) {
    ThreadLocal<?> k = e.get();
    if (k == key)
      return e;
    if (k == null)
      expungeStaleEntry(i);
    else
      i = nextIndex(i, len);
    e = tab[i];
  }
  return null;
}

我們上文一直說,ThreadLocal是保存在單個線程中的數(shù)據(jù),每個線程都有自己的數(shù)據(jù),但是實際ThreadLocal里面的真正的對象數(shù)據(jù),其實是保存在堆里面的,而線程里面只是存儲了對象的引用而已。
并且我們在使用的時候通常需要在上一個線程執(zhí)行的方法的上下文共享ThreadLocal中的變量。
例如我的主線程是在某個方法中執(zhí)行代碼呢,但是這個方法中有一段代碼時新創(chuàng)建了一個線程,在這個線程里面還使用了我這個正在執(zhí)行的方法里面的定義的ThreadLocal里面的變量。這個時候,就是需要從新線程里面調(diào)用外面線程的數(shù)據(jù),這個就需要線程間共享了。這種子父線程共享數(shù)據(jù)的情況,ThreadLocal也是支持的。
例如:

 ThreadLocal threadLocalMain = new InheritableThreadLocal();
 threadLocalMain.set("主線程變量");
 Thread t = new Thread() {
   @Override
   public void run() {
     super.run();
     System.out.println( "現(xiàn)在獲取的變量是 =" + threadLocalMain.get());
   }
 };
 t.start();

運行結果:

現(xiàn)在獲取的變量是 =主線程變量

上面這樣的代碼就能實現(xiàn)子父線程共享數(shù)據(jù)的情況,重點是使用InheritableThreadLocal來實現(xiàn)的共享。
那么它是怎么實現(xiàn)數(shù)據(jù)共享的呢?
在Thread類的init()方法中有這么一段代碼:

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
      this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

這段代碼的意思是,在創(chuàng)建線程的時候,如果當前線程的inheritThreadLocals變量和父線程的inheritThreadLocals變量都不為空的時候,會將父線程的inheritThreadLocals變量中的數(shù)據(jù),賦給當前線程中的inheritThreadLocals變量。

ThreadLocal的內(nèi)存泄漏問題
上文我們也提到過,ThreadLocal中的ThreadLocalMap里面的Entry對象是繼承自WeakReference類的,說明Entry的key是一個弱引用。

弱引用是用來描述那些非必須的對象,弱引用的對象,只能生存到下一次垃圾收集發(fā)生為止。當垃圾收集器開始工作,無論當前內(nèi)存是否足夠,都會回收掉只被弱引用關聯(lián)的對象。

這個弱引用還是ThreadLocal對象本身,所以一般在線程執(zhí)行完成后,ThreadLocal對象就會變成null了,而為null的弱引用對象,在下一次GC的時候就會被清除掉,這樣Entry的Key的內(nèi)存空間就被釋放出來了,但是Entry的value還在占用的內(nèi)存,如果線程是被復用的(例如線程池中的線程),那么這里面的value值就會越來越多,最終就導致了內(nèi)存泄漏。

防止內(nèi)存泄漏的辦法就是在每次使用完ThreadLocal的時候都去執(zhí)行以下remove()方法,就可以把key和value的空間都釋放了。

那既然容易產(chǎn)生內(nèi)存泄漏,為什么還要設置成弱引用的呢?
如果正常情況下應該是強引用,但是強引用只要引用關系還在就一直不會被回收,所以如果線程被復用了,那么Entry中的Key和Value都不會被回收,這樣就造成了Key和Value都會發(fā)生內(nèi)存泄漏了。

以上就是Java中ThreadLocal的一些理解的詳細內(nèi)容,更多關于Java ThreadLocal的資料請關注腳本之家其它相關文章!

相關文章

  • java后臺本地文件轉為MultipartFile類型的實現(xiàn)方式

    java后臺本地文件轉為MultipartFile類型的實現(xiàn)方式

    在Java后臺將本地文件轉換為MultipartFile類型,可以通過使用FileItemFactory創(chuàng)建FileItem,然后使用CommonsMultipartFile類構造一個MultipartFile對象,將本地文件流轉換為MultipartFile,getMultipartFiles()和getMultipartFiles()方法
    2025-02-02
  • Java中的反射機制示例詳解

    Java中的反射機制示例詳解

    反射就是把Java類中的各個成分映射成一個個的Java對象。本文將通過示例詳細講解Java中的反射機制,感興趣的小伙伴可以跟隨小編學習一下
    2022-03-03
  • java的各種集合為什么不安全(List、Set、Map)以及代替方案

    java的各種集合為什么不安全(List、Set、Map)以及代替方案

    這篇文章主要介紹了java的各種集合為什么不安全(List、Set、Map)以及代替方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-10-10
  • spring自定義一個簡單的Starter啟動器

    spring自定義一個簡單的Starter啟動器

    這篇文章主要介紹了spring自定義一個簡單的Starter啟動器,一個 starter其實就是對一個功能的集成封裝,然后對外提供一個依賴,讓業(yè)務去使用,像我們熟悉的 Redis,mongo,mybatis 等均屬于,需要的朋友可以參考下
    2023-07-07
  • Mybatis-plus批量插入的2種方式總結

    Mybatis-plus批量插入的2種方式總結

    這篇文章主要給大家總結介紹了關于Mybatis-plus批量插入的2種方式,Mybatis-Plus提供了多種方式進行批量插入優(yōu)化,文中通過代碼示例將實現(xiàn)的方法介紹的非常詳細,需要的朋友可以參考下
    2023-08-08
  • Java學習之Lambda表達式的使用詳解

    Java學習之Lambda表達式的使用詳解

    Lambda表達式是Java SE 8中一個重要的新特性,允許通過表達式來代替功能接口。本文將通過一些簡單的示例和大家講講Lamda表達式的使用,感興趣的可以了解一下
    2022-12-12
  • java必懂的冷知識點之Base64加密與解密

    java必懂的冷知識點之Base64加密與解密

    這篇文章主要介紹了java必懂的冷知識點之Base64加密與解密的相關資料,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • spring-mybatis獲取mapper的四種方式匯總

    spring-mybatis獲取mapper的四種方式匯總

    這篇文章主要介紹了spring-mybatis獲取mapper的四種方式匯總,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Java中的final關鍵字使用方式

    Java中的final關鍵字使用方式

    這篇文章主要介紹了Java中的final關鍵字使用方式,final 關鍵字用于修飾不可改變內(nèi)容,更多相關梳理總結,需要的小伙伴可以參考下面文章內(nèi)容
    2022-06-06
  • 詳解Java的Struts框架中上傳文件和客戶端驗證的實現(xiàn)

    詳解Java的Struts框架中上傳文件和客戶端驗證的實現(xiàn)

    這篇文章主要介紹了Java的Struts框架中上傳文件和客戶端驗證的實現(xiàn),Struts是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下
    2015-12-12

最新評論

大庆市| 四子王旗| 永登县| 建平县| 疏附县| 盈江县| 六安市| 永寿县| 定远县| 喀喇沁旗| 攀枝花市| 五大连池市| 嘉鱼县| 十堰市| 岢岚县| 秦皇岛市| 青海省| 大同县| 桐庐县| 蓬莱市| 永宁县| 洛扎县| 易门县| 徐水县| 龙山县| 天峨县| 普陀区| 南汇区| 隆安县| 阿合奇县| 东山县| 海门市| 正安县| 惠来县| 精河县| 吐鲁番市| 康保县| 米林县| 芦山县| 泗水县| 神农架林区|