成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

ThreadLocal使用與原理

開發(fā) 前端
今天,我們來介紹ThreadLocal,ThreadLocal在Java的多線程開發(fā)中有著十分重要的作用。我們還介紹ThreadLocal的基本使用和實現(xiàn)原理,尤其重點介紹了基于當(dāng)前實現(xiàn)原理下可能存在的內(nèi)存泄漏問題。

[[397456]]

在處理多線程并發(fā)安全的方法中,最常用的方法,就是使用鎖,通過鎖來控制多個不同線程對臨界區(qū)的訪問。

但是,無論是什么樣的鎖,樂觀鎖或者悲觀鎖,都會在并發(fā)沖突的時候?qū)π阅墚a(chǎn)生一定的影響。

那有沒有一種方法,可以徹底避免競爭呢?

答案是肯定的,這就是ThreadLocal。

從字面意思上看,ThreadLocal可以解釋成線程的局部變量,也就是說一個ThreadLocal的變量只有當(dāng)前自身線程可以訪問,別的線程都訪問不了,那么自然就避免了線程競爭。

因此,ThreadLocal提供了一種與眾不同的線程安全方式,它不是在發(fā)生線程沖突時想辦法解決沖突,而是徹底的避免了沖突的發(fā)生。

ThreadLocal的基本使用

創(chuàng)建一個ThreadLocal對象:

  1. private ThreadLocal<Integer> localInt = new ThreadLocal<>(); 

上述代碼創(chuàng)建一個localInt變量,由于ThreadLocal是一個泛型類,這里指定了localInt的類型為整數(shù)。

下面展示了如果設(shè)置和獲取這個變量的值:

  1. public int setAndGet(){ 
  2.     localInt.set(8); 
  3.     return localInt.get(); 

上述代碼設(shè)置變量的值為8,接著取得這個值。

由于ThreadLocal里設(shè)置的值,只有當(dāng)前線程自己看得見,這意味著你不可能通過其他線程為它初始化值。為了彌補這一點,ThreadLocal提供了一個withInitial()方法統(tǒng)一初始化所有線程的ThreadLocal的值:

  1. private ThreadLocal<Integer> localInt = ThreadLocal.withInitial(() -> 6); 

上述代碼將ThreadLocal的初始值設(shè)置為6,這對全體線程都是可見的。

ThreadLocal的實現(xiàn)原理

ThreadLocal變量只在單個線程內(nèi)可見,那它是如何做到的呢?我們先從最基本的get()方法說起:

  1. public T get() { 
  2.     //獲得當(dāng)前線程 
  3.     Thread t = Thread.currentThread(); 
  4.     //每個線程 都有一個自己的ThreadLocalMap, 
  5.     //ThreadLocalMap里就保存著所有的ThreadLocal變量 
  6.     ThreadLocalMap map = getMap(t); 
  7.     if (map != null) { 
  8.         //ThreadLocalMap的key就是當(dāng)前ThreadLocal對象實例, 
  9.         //多個ThreadLocal變量都是放在這個map中的 
  10.         ThreadLocalMap.Entry e = map.getEntry(this); 
  11.         if (e != null) { 
  12.             @SuppressWarnings("unchecked"
  13.             //從map里取出來的值就是我們需要的這個ThreadLocal變量 
  14.             T result = (T)e.value; 
  15.             return result; 
  16.         } 
  17.     } 
  18.     // 如果map沒有初始化,那么在這里初始化一下 
  19.     return setInitialValue(); 

可以看到,所謂的ThreadLocal變量就是保存在每個線程的map中的。這個map就是Thread對象中的threadLocals字段。如下:

  1. ThreadLocal.ThreadLocalMap threadLocals = null

ThreadLocal.ThreadLocalMap是一個比較特殊的Map,它的每個Entry的key都是一個弱引用:

  1. static class Entry extends WeakReference<ThreadLocal<?>> { 
  2.     /** The value associated with this ThreadLocal. */ 
  3.     Object value; 
  4.     //key就是一個弱引用 
  5.     Entry(ThreadLocal<?> k, Object v) { 
  6.         super(k); 
  7.         value = v; 
  8.     } 

這樣設(shè)計的好處是,如果這個變量不再被其他對象使用時,可以自動回收這個ThreadLocal對象,避免可能的內(nèi)存泄露(注意,Entry中的value,依然是強引用,如何回收,見下文分解)。

理解ThreadLocal中的內(nèi)存泄漏問題

雖然ThreadLocalMap中的key是弱引用,當(dāng)不存在外部強引用的時候,就會自動被回收,但是Entry中的value依然是強引用。這個value的引用鏈條如下:

可以看到,只有當(dāng)Thread被回收時,這個value才有被回收的機會,否則,只要線程不退出,value總是會存在一個強引用。但是,要求每個Thread都會退出,是一個極其苛刻的要求,對于線程池來說,大部分線程會一直存在在系統(tǒng)的整個生命周期內(nèi),那樣的話,就會造成value對象出現(xiàn)泄漏的可能。處理的方法是,在ThreadLocalMap進行set(),get(),remove()的時候,都會進行清理:

以getEntry()為例:

  1. private Entry getEntry(ThreadLocal<?> key) { 
  2.     int i = key.threadLocalHashCode & (table.length - 1); 
  3.     Entry e = table[i]; 
  4.     if (e != null && e.get() == key
  5.         //如果找到key,直接返回 
  6.         return e; 
  7.     else 
  8.         //如果找不到,就會嘗試清理,如果你總是訪問存在的key,那么這個清理永遠不會進來 
  9.         return getEntryAfterMiss(key, i, e); 

下面是getEntryAfterMiss()的實現(xiàn):

  1. private Entry getEntryAfterMiss(ThreadLocal<?> keyint i, Entry e) { 
  2.     Entry[] tab = table
  3.     int len = tab.length; 
  4.  
  5.     while (e != null) { 
  6.         // 整個e是entry ,也就是一個弱引用 
  7.         ThreadLocal<?> k = e.get(); 
  8.         //如果找到了,就返回 
  9.         if (k == key
  10.             return e; 
  11.         if (k == null
  12.             //如果keynull,說明弱引用已經(jīng)被回收了 
  13.             //那么就要在這里回收里面的value了 
  14.             expungeStaleEntry(i); 
  15.         else 
  16.             //如果key不是要找的那個,那說明有hash沖突,這里是處理沖突,找下一個entry 
  17.             i = nextIndex(i, len); 
  18.         e = tab[i]; 
  19.     } 
  20.     return null

真正用來回收value的是expungeStaleEntry()方法,在remove()和set()方法中,都會直接或者間接調(diào)用到這個方法進行value的清理:

從這里可以看到,ThreadLocal為了避免內(nèi)存泄露,也算是花了一番大心思。不僅使用了弱引用維護key,還會在每個操作上檢查key是否被回收,進而再回收value。

但是從中也可以看到,ThreadLocal并不能100%保證不發(fā)生內(nèi)存泄漏。

比如,很不幸的,你的get()方法總是訪問固定幾個一直存在的ThreadLocal,那么清理動作就不會執(zhí)行,如果你沒有機會調(diào)用set()和remove(),那么這個內(nèi)存泄漏依然會發(fā)生。

因此,一個良好的習(xí)慣依然是:當(dāng)你不需要這個ThreadLocal變量時,主動調(diào)用remove(),這樣對整個系統(tǒng)是有好處的。

ThreadLocalMap中的Hash沖突處理

ThreadLocalMap作為一個HashMap和java.util.HashMap的實現(xiàn)是不同的。對于java.util.HashMap使用的是鏈表法來處理沖突:

但是,對于ThreadLocalMap,它使用的是簡單的線性探測法,如果發(fā)生了元素沖突,那么就使用下一個槽位存放:

具體來說,整個set()的過程如下:

可以被繼承的ThreadLocal——InheritableThreadLocal

在實際開發(fā)過程中,我們可能會遇到這么一種場景。主線程開了一個子線程,但是我們希望在子線程中可以訪問主線程中的ThreadLocal對象,也就是說有些數(shù)據(jù)需要進行父子線程間的傳遞。比如像這樣:

  1. public static void main(String[] args) { 
  2.     ThreadLocal threadLocal = new ThreadLocal(); 
  3.     IntStream.range(0,10).forEach(i -> { 
  4.         //每個線程的序列號,希望在子線程中能夠拿到 
  5.         threadLocal.set(i); 
  6.         //這里來了一個子線程,我們希望可以訪問上面的threadLocal 
  7.         new Thread(() -> { 
  8.             System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); 
  9.         }).start(); 
  10.         try { 
  11.             Thread.sleep(1000); 
  12.         } catch (InterruptedException e) { 
  13.             e.printStackTrace(); 
  14.         } 
  15.     }); 

執(zhí)行上述代碼,你會看到:

  1. Thread-0:null 
  2. Thread-1:null 
  3. Thread-2:null 
  4. Thread-3:null 

因為在子線程中,是沒有threadLocal的。如果我們希望子線可以看到父線程的ThreadLocal,那么就可以使用InheritableThreadLocal。顧名思義,這就是一個支持線程間父子繼承的ThreadLocal,將上述代碼中的threadLocal使用InheritableThreadLocal:

  1. InheritableThreadLocal threadLocal = new InheritableThreadLocal(); 

再執(zhí)行,就能看到:

  1. Thread-0:0 
  2. Thread-1:1 
  3. Thread-2:2 
  4. Thread-3:3 
  5. Thread-4:4 

可以看到,每個線程都可以訪問到從父進程傳遞過來的一個數(shù)據(jù)。雖然InheritableThreadLocal看起來挺方便的,但是依然要注意以下幾點:

變量的傳遞是發(fā)生在線程創(chuàng)建的時候,如果不是新建線程,而是用了線程池里的線程,就不靈了

變量的賦值就是從主線程的map復(fù)制到子線程,它們的value是同一個對象,如果這個對象本身不是線程安全的,那么就會有線程安全問題

寫在最后的話

今天,我們介紹了ThreadLocal,ThreadLocal在Java的多線程開發(fā)中有著十分重要的作用。

在這里,我們介紹了ThreadLocal的基本使用和實現(xiàn)原理,尤其重點介紹了基于當(dāng)前實現(xiàn)原理下可能存在的內(nèi)存泄漏問題。

最后,還介紹了一個用于在父子線程間傳遞數(shù)據(jù)的特殊的ThreadLocal實現(xiàn),希望對大家有所幫助。

 

責(zé)任編輯:姜華 來源: 三太子敖丙
相關(guān)推薦

2022-03-17 08:55:43

本地線程變量共享全局變量

2023-09-08 08:20:46

ThreadLoca多線程工具

2015-09-09 08:45:49

JavaThreadLocal

2023-02-28 11:27:50

線程處理解決共享變量

2024-11-18 16:15:00

2024-10-28 08:15:32

2023-05-29 07:17:48

內(nèi)存溢出場景

2025-02-26 08:13:23

2018-04-09 08:17:36

線程ThreadLocal數(shù)據(jù)

2020-12-09 08:21:47

編程Exchanger工具

2020-12-03 11:15:21

CyclicBarri

2009-09-29 17:11:23

Hibernate T

2011-07-14 13:50:09

ThreadLocal

2025-06-27 07:19:48

2022-05-09 07:27:50

ThreadLocaJava

2021-12-31 18:24:45

ThreadLocal數(shù)據(jù)庫對象

2023-08-02 08:54:58

Java弱引用鏈表

2022-05-11 07:36:12

Java線程安全

2023-10-07 08:26:40

多線程數(shù)據(jù)傳遞數(shù)據(jù)共享

2021-01-19 05:24:36

ThreadLocal線程編程
點贊
收藏

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

主站蜘蛛池模板: 自拍第1页 | 久久一区视频 | 麻豆一区二区三区 | av片网站| 成人久久18免费网站图片 | 久久久www| 亚洲国产一区二区三区在线观看 | 欧美一级大黄 | 羞羞视频网站免费看 | 日韩成人一区 | 精品国产第一区二区三区 | 99精品电影| 亚洲一区二区中文字幕 | 国产一区二区在线播放视频 | 综合久久综合久久 | 91激情电影| 精品国产欧美一区二区三区不卡 | 亚洲欧美中文日韩在线v日本 | 亚洲成人三区 | 一区二区在线 | 成人性生交大片免费看r链接 | 日韩色在线 | 日日摸夜夜爽人人添av | 国产精品久久久久久久久久久久 | 亚洲网站在线观看 | 日韩视频在线一区 | 欧美亚洲国产日韩 | 中文字幕国产第一页 | 免费激情网站 | 日韩av成人在线 | 亚洲乱码一区二区三区在线观看 | 91精品在线播放 | 久久九九免费 | 欧美日韩国产在线观看 | 亚洲高清视频一区二区 | 久久久久久久一区二区三区 | www.国产| 韩国精品一区 | 午夜大片 | 亚洲免费大片 | 亚洲欧美在线视频 |