久久综合九色综合97婷婷-美女视频黄频a免费-精品日本一区二区三区在线观看-日韩中文无码有码免费视频-亚洲中文字幕无码专区-扒开双腿疯狂进出爽爽爽动态照片-国产乱理伦片在线观看夜-高清极品美女毛茸茸-欧美寡妇性猛交XXX-国产亚洲精品99在线播放-日韩美女毛片又爽又大毛片,99久久久无码国产精品9,国产成a人片在线观看视频下载,欧美疯狂xxxx吞精视频

有趣生活

當(dāng)前位置:首頁>職場>threadlocal面試攻略(面試必備ThreadLocal詳解)

threadlocal面試攻略(面試必備ThreadLocal詳解)

發(fā)布時間:2024-01-24閱讀(11)

導(dǎo)讀大家好,我是撿田螺的小男孩,下面我們就來說一說關(guān)于threadlocal面試攻略?我們一起去了解并探討一下這個問題吧!threadlocal面試攻略前言大家....

大家好,我是撿田螺的小男孩,下面我們就來說一說關(guān)于threadlocal面試攻略?我們一起去了解并探討一下這個問題吧!

threadlocal面試攻略(面試必備ThreadLocal詳解)

threadlocal面試攻略

前言

大家好,我是撿田螺的小男孩

無論是工作還是面試,我們都會跟ThreadLocal打交道,今天就跟大家聊聊ThreadLocal哈~

  1. ThreadLocal是什么?為什么要使用ThreadLocal
  2. 一個ThreadLocal的使用案例
  3. ThreadLocal的原理
  4. 為什么不直接用線程id作為ThreadLocalMap的key
  5. 為什么會導(dǎo)致內(nèi)存泄漏呢?是因為弱引用嗎?
  6. Key為什么要設(shè)計成弱引用呢?強引用不行?
  7. InheritableThreadLocal保證父子線程間的共享數(shù)據(jù)
  8. ThreadLocal的應(yīng)用場景和使用注意點
  • github地址,麻煩給個star鼓勵一下,感謝感謝
  • 公眾號:撿田螺的小男孩(歡迎關(guān)注,干貨多多)1. ThreadLocal是什么?為什么要使用ThreadLocal?

    ThreadLocal是什么?

    ThreadLocal,即線程本地變量。如果你創(chuàng)建了一個ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個本地拷貝,多個線程操作這個變量的時候,實際是在操作自己本地內(nèi)存里面的變量,從而起到線程隔離的作用,避免了并發(fā)場景下的線程安全問題。

    //創(chuàng)建一個ThreadLocal變量static ThreadLocal<String> localVariable = new ThreadLocal<>();復(fù)制代碼

    為什么要使用ThreadLocal

    并發(fā)場景下,會存在多個線程同時修改一個共享變量的場景。這就可能會出現(xiàn)線性安全問題

    為了解決線性安全問題,可以用加鎖的方式,比如使用synchronized 或者Lock。但是加鎖的方式,可能會導(dǎo)致系統(tǒng)變慢。加鎖示意圖如下:

    還有另外一種方案,就是使用空間換時間的方式,即使用ThreadLocal。使用ThreadLocal類訪問共享變量時,會在每個線程的本地,都保存一份共享變量的拷貝副本。多線程對共享變量修改時,實際上操作的是這個變量副本,從而保證線性安全。

    2. 一個ThreadLocal的使用案例

    日常開發(fā)中,ThreadLocal經(jīng)常在日期轉(zhuǎn)換工具類中出現(xiàn),我們先來看個反例

    /** * 日期工具類 */public class DateUtil { private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static Date parse(String dateString) { Date date = null; try { date = simpleDateFormat.parse(dateString); } catch (ParseException e) { e.printStackTrace(); } return date; }}復(fù)制代碼

    我們在多線程環(huán)境跑DateUtil這個工具類:

    public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i ) { executorService.execute(()->{ System.out.println(DateUtil.parse("2022-07-24 16:34:30")); }); } executorService.shutdown(); }復(fù)制代碼

    運行后,發(fā)現(xiàn)報錯了:

    如果在DateUtil工具類,加上ThreadLocal,運行則不會有這個問題:

    /** * 日期工具類 */public class DateUtil { private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); public static Date parse(String dateString) { Date date = null; try { date = dateFormatThreadLocal.get().parse(dateString); } catch (ParseException e) { e.printStackTrace(); } return date; } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i ) { executorService.execute(()->{ System.out.println(DateUtil.parse("2022-07-24 16:34:30")); }); } executorService.shutdown(); }}復(fù)制代碼

    運行結(jié)果:

    Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022Sun Jul 24 16:34:30 GMT 08:00 2022復(fù)制代碼

    剛剛反例中,為什么會報錯呢?這是因為SimpleDateFormat不是線性安全的,它以共享變量出現(xiàn)時,并發(fā)多線程場景下即會報錯。

    為什么加了ThreadLocal就不會有問題呢?并發(fā)場景下,ThreadLocal是如何保證的呢?我們接下來看看ThreadLocal的核心原理。

    3. ThreadLocal的原理3.1 ThreadLocal的內(nèi)存結(jié)構(gòu)圖

    為了有個宏觀的認(rèn)識,我們先來看下ThreadLocal的內(nèi)存結(jié)構(gòu)圖

    從內(nèi)存結(jié)構(gòu)圖,我們可以看到:

  • Thread類中,有個ThreadLocal.ThreadLocalMap 的成員變量。
  • ThreadLocalMap內(nèi)部維護(hù)了Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型對象值。3.2 關(guān)鍵源碼分析

    對照著幾段關(guān)鍵源碼來看,更容易理解一點哈~我們回到Thread類源碼,可以看到成員變量ThreadLocalMap的初始值是為null

    public class Thread implements Runnable { //ThreadLocal.ThreadLocalMap是Thread的屬性 ThreadLocal.ThreadLocalMap threadLocals = null;}復(fù)制代碼

    ThreadLocalMap的關(guān)鍵源碼如下:

    static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //Entry數(shù)組 private Entry[] table; // ThreadLocalMap的構(gòu)造器,ThreadLocal作為key 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); }}復(fù)制代碼

    ThreadLocal類中的關(guān)鍵set()方法:

    public void set(T value) { Thread t = Thread.currentThread(); //獲取當(dāng)前線程t ThreadLocalMap map = getMap(t); //根據(jù)當(dāng)前線程獲取到ThreadLocalMap if (map != null) //如果獲取的ThreadLocalMap對象不為空 map.set(this, value); //K,V設(shè)置到ThreadLocalMap中 else createMap(t, value); //創(chuàng)建一個新的ThreadLocalMap } ThreadLocalMap getMap(Thread t) { return t.threadLocals; //返回Thread對象的ThreadLocalMap屬性 } void createMap(Thread t, T firstValue) { //調(diào)用ThreadLocalMap的構(gòu)造函數(shù) t.threadLocals = new ThreadLocalMap(this, firstValue); this表示當(dāng)前類ThreadLocal } 復(fù)制代碼

    ThreadLocal類中的關(guān)鍵get()方法

    public T get() { Thread t = Thread.currentThread();//獲取當(dāng)前線程t ThreadLocalMap map = getMap(t);//根據(jù)當(dāng)前線程獲取到ThreadLocalMap if (map != null) { //如果獲取的ThreadLocalMap對象不為空 //由this(即ThreadLoca對象)得到對應(yīng)的Value,即ThreadLocal的泛型值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); //初始化threadLocals成員變量的值 } private T setInitialValue() { T value = initialValue(); //初始化value的值 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //以當(dāng)前線程為key,獲取threadLocals成員變量,它是一個ThreadLocalMap if (map != null) map.set(this, value); //K,V設(shè)置到ThreadLocalMap中 else createMap(t, value); //實例化threadLocals成員變量 return value; }復(fù)制代碼

    所以怎么回答ThreadLocal的實現(xiàn)原理?如下,最好是能結(jié)合以上結(jié)構(gòu)圖一起說明哈~

  • Thread線程類有一個類型為ThreadLocal.ThreadLocalMap的實例變量threadLocals,即每個線程都有一個屬于自己的ThreadLocalMap。
  • ThreadLocalMap內(nèi)部維護(hù)著Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
  • 并發(fā)多線程場景下,每個線程Thread,在往ThreadLocal里設(shè)置值的時候,都是往自己的ThreadLocalMap里存,讀也是以某個ThreadLocal作為引用,在自己的map里找對應(yīng)的key,從而可以實現(xiàn)了線程隔離

    了解完這幾個核心方法后,有些小伙伴可能會有疑惑,ThreadLocalMap為什么要用ThreadLocal作為key呢?直接用線程Id不一樣嘛?

    4. 為什么不直接用線程id作為ThreadLocalMap的key呢?

    舉個代碼例子,如下:

    public class TianLuoThreadLocalTest { private static final ThreadLocal<String> threadLocal1 = new ThreadLocal<>(); private static final ThreadLocal<String> threadLocal2 = new ThreadLocal<>(); }復(fù)制代碼

    這種場景:一個使用類,有兩個共享變量,也就是說用了兩個ThreadLocal成員變量的話。如果用線程id作為ThreadLocalMap的key,怎么區(qū)分哪個ThreadLocal成員變量呢?因此還是需要使用ThreadLocal作為Key來使用。每個ThreadLocal對象,都可以由threadLocalHashCode屬性唯一區(qū)分的,每一個ThreadLocal對象都可以由這個對象的名字唯一區(qū)分(下面的例子)。看下ThreadLocal代碼:

    public class ThreadLocal<T> { private final int threadLocalHashCode = nextHashCode(); private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }}

    然后我們再來看下一個代碼例子:

    public class TianLuoThreadLocalTest { public static void main(String[] args) { Thread t = new Thread(new Runnable(){ public void run(){ ThreadLocal<TianLuoDTO> threadLocal1 = new ThreadLocal<>(); threadLocal1.set(new TianLuoDTO("公眾號:撿田螺的小男孩")); System.out.println(threadLocal1.get()); ThreadLocal<TianLuoDTO> threadLocal2 = new ThreadLocal<>(); threadLocal2.set(new TianLuoDTO("公眾號:程序員田螺")); System.out.println(threadLocal2.get()); }}); t.start(); }}//運行結(jié)果TianLuoDTO{name=公眾號:撿田螺的小男孩}TianLuoDTO{name=公眾號:程序員田螺}復(fù)制代碼

    再對比下這個圖,可能就更清晰一點啦:

    5. TreadLocal為什么會導(dǎo)致內(nèi)存泄漏呢?5.1 弱引用導(dǎo)致的內(nèi)存泄漏呢?

    我們先來看看TreadLocal的引用示意圖哈:

    關(guān)于ThreadLocal內(nèi)存泄漏,網(wǎng)上比較流行的說法是這樣的:

    ThreadLocalMap使用ThreadLocal的弱引用作為key,當(dāng)ThreadLocal變量被手動設(shè)置為null,即一個ThreadLocal沒有外部強引用來引用它,當(dāng)系統(tǒng)GC時,ThreadLocal一定會被回收。這樣的話,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話(比如線程池的核心線程),這些key為null的Entry的value就會一直存在一條強引用鏈:Thread變量 -> Thread對象 -> ThreaLocalMap -> Entry -> value -> Object 永遠(yuǎn)無法回收,造成內(nèi)存泄漏。

    當(dāng)ThreadLocal變量被手動設(shè)置為null后的引用鏈圖:

    實際上,ThreadLocalMap的設(shè)計中已經(jīng)考慮到這種情況。所以也加上了一些防護(hù)措施:即在ThreadLocal的get,set,remove方法,都會清除線程ThreadLocalMap里所有key為null的value。

    源代碼中,是有體現(xiàn)的,如ThreadLocalMap的set方法:

    private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } //如果k等于null,則說明該索引位之前放的key(threadLocal對象)被回收了,這通常是因為外部將threadLocal變量置為null, //又因為entry對threadLocal持有的是弱引用,一輪GC過后,對象被回收。 //這種情況下,既然用戶代碼都已經(jīng)將threadLocal置為null,那么也就沒打算再通過該對象作為key去取到之前放入threadLocalMap的value, 因此ThreadLocalMap中會直接替換調(diào)這種不新鮮的entry。 if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = size; //觸發(fā)一次Log2(N)復(fù)雜度的掃描,目的是清除過期Entry if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }復(fù)制代碼

    如ThreadLocal的get方法:

    public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { //去ThreadLocalMap獲取Entry,方法里面有key==null的清除邏輯 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();}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 //里面有key==null的清除邏輯 return getEntryAfterMiss(key, i, e); } 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; // Entry的key為null,則表明沒有外部引用,且被GC回收,是一個過期Entry if (k == null) expungeStaleEntry(i); //刪除過期的Entry else i = nextIndex(i, len); e = tab[i]; } return null; }復(fù)制代碼

    5.2 key是弱引用,GC回收會影響ThreadLocal的正常工作嘛?

    到這里,有些小伙伴可能有疑問,ThreadLocal的key既然是弱引用.會不會GC貿(mào)然把key回收掉,進(jìn)而影響ThreadLocal的正常使用?

    弱引用:具有弱引用的對象擁有更短暫的生命周期。如果一個對象只有弱引用存在了,則下次GC將會回收掉該對象(不管當(dāng)前內(nèi)存空間足夠與否)

    其實不會的,因為有ThreadLocal變量引用著它,是不會被GC回收的,除非手動把ThreadLocal變量設(shè)置為null,我們可以跑個demo來驗證一下:

    public class WeakReferenceTest { public static void main(String[] args) { Object object = new Object(); WeakReference<Object> testWeakReference = new WeakReference<>(object); System.out.println("GC回收之前,弱引用:" testWeakReference.get()); //觸發(fā)系統(tǒng)垃圾回收 System.gc(); System.out.println("GC回收之后,弱引用:" testWeakReference.get()); //手動設(shè)置為object對象為null object=null; System.gc(); System.out.println("對象object設(shè)置為null,GC回收之后,弱引用:" testWeakReference.get()); }}運行結(jié)果:GC回收之前,弱引用:java.lang.Object@7b23ec81GC回收之后,弱引用:java.lang.Object@7b23ec81對象object設(shè)置為null,GC回收之后,弱引用:null復(fù)制代碼

    結(jié)論就是,小伙伴放下這個疑惑了,哈哈~

    5.3 ThreadLocal內(nèi)存泄漏的demo

    給大家來看下一個內(nèi)存泄漏的例子,其實就是用線程池,一直往里面放對象

    public class ThreadLocalTestDemo { private static ThreadLocal<TianLuoClass> tianLuoThreadLocal = new ThreadLocal<>(); public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>()); for (int i = 0; i < 10; i) { threadPoolExecutor.execute(new Runnable() { @Override public void run() { System.out.println("創(chuàng)建對象:"); TianLuoClass tianLuoClass = new TianLuoClass(); tianLuoThreadLocal.set(tianLuoClass); tianLuoClass = null; //將對象設(shè)置為 null,表示此對象不在使用了 // tianLuoThreadLocal.remove(); } }); Thread.sleep(1000); } } static class TianLuoClass { // 100M private byte[] bytes = new byte[100 * 1024 * 1024]; }}創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:Exception in thread "pool-1-thread-4" java.lang.OutOfMemoryError: Java heap spaceat com.example.dto.ThreadLocalTestDemo$TianLuoClass.<init>(ThreadLocalTestDemo.java:33)at com.example.dto.ThreadLocalTestDemo$1.run(ThreadLocalTestDemo.java:21)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)復(fù)制代碼

    運行結(jié)果出現(xiàn)了OOM,tianLuoThreadLocal.remove();加上后,則不會OOM。

    創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:創(chuàng)建對象:......復(fù)制代碼

    我們這里沒有手動設(shè)置tianLuoThreadLocal變量為null,但是還是會內(nèi)存泄漏。因為我們使用了線程池,線程池有很長的生命周期,因此線程池會一直持有tianLuoClass對象的value值,即使設(shè)置tianLuoClass = null;引用還是存在的。這就好像,你把一個個對象object放到一個list列表里,然后再單獨把object設(shè)置為null的道理是一樣的,列表的對象還是存在的。

    public static void main(String[] args) { List<Object> list = new ArrayList<>(); Object object = new Object(); list.add(object); object = null; System.out.println(list.size()); } //運行結(jié)果 1復(fù)制代碼

    所以內(nèi)存泄漏就這樣發(fā)生啦,最后內(nèi)存是有限的,就拋出了OOM了。如果我們加上threadLocal.remove();,則不會內(nèi)存泄漏。為什么呢?因為threadLocal.remove();會清除Entry,源碼如下:

    private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //清除entry e.clear(); expungeStaleEntry(i); return; } }}復(fù)制代碼

    有些小伙伴說,既然內(nèi)存泄漏不一定是因為弱引用,那為什么需要設(shè)計為弱引用呢?我們來探討下:

    6. Entry的Key為什么要設(shè)計成弱引用呢?

    通過源碼,我們是可以看到Entry的Key是設(shè)計為弱引用的(ThreadLocalMap使用ThreadLocal的弱引用作為Key的)。為什么要設(shè)計為弱引用呢?

    我們先來回憶一下四種引用:

  • 強引用:我們平時new了一個對象就是強引用,例如 Object obj = new Object();即使在內(nèi)存不足的情況下,JVM寧愿拋出OutOfMemory錯誤也不會回收這種對象。
  • 軟引用:如果一個對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。
  • 弱引用:具有弱引用的對象擁有更短暫的生命周期。如果一個對象只有弱引用存在了,則下次GC將會回收掉該對象(不管當(dāng)前內(nèi)存空間足夠與否)。
  • 虛引用:如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。

    我們先來看看官方文檔,為什么要設(shè)計為弱引用:

    To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.為了應(yīng)對非常大和長時間的用途,哈希表使用弱引用的 key。復(fù)制代碼

    我再把ThreadLocal的引用示意圖搬過來:

    下面我們分情況討論:

  • 如果Key使用強引用:當(dāng)ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用的話,如果沒有手動刪除,ThreadLocal就不會被回收,會出現(xiàn)Entry的內(nèi)存泄漏問題。
  • 如果Key使用弱引用:當(dāng)ThreadLocal的對象被回收了,因為ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value則在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。

    因此可以發(fā)現(xiàn),使用弱引用作為Entry的Key,可以多一層保障:弱引用ThreadLocal不會輕易內(nèi)存泄漏,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。

    實際上,我們的內(nèi)存泄漏的根本原因是,不再被使用的Entry,沒有從線程的ThreadLocalMap中刪除。一般刪除不再使用的Entry有這兩種方式:

  • 一種就是,使用完ThreadLocal,手動調(diào)用remove(),把Entry從ThreadLocalMap中刪除
  • 另外一種方式就是:ThreadLocalMap的自動清除機制去清除過期Entry.(ThreadLocalMap的get(),set()時都會觸發(fā)對過期Entry的清除)7. InheritableThreadLocal保證父子線程間的共享數(shù)據(jù)

    我們知道ThreadLocal是線程隔離的,如果我們希望父子線程共享數(shù)據(jù),如何做到呢?可以使用InheritableThreadLocal。先來看看demo:

    public class InheritableThreadLocalTest { public static void main(String[] args) { ThreadLocal<String> threadLocal = new ThreadLocal<>(); InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>(); threadLocal.set("關(guān)注公眾號:撿田螺的小男孩"); inheritableThreadLocal.set("關(guān)注公眾號:程序員田螺"); Thread thread = new Thread(()->{ System.out.println("ThreadLocal value " threadLocal.get()); System.out.println("InheritableThreadLocal value " inheritableThreadLocal.get()); }); thread.start(); }}//運行結(jié)果ThreadLocal value nullInheritableThreadLocal value 關(guān)注公眾號:程序員田螺復(fù)制代碼

    可以發(fā)現(xiàn),在子線程中,是可以獲取到父線程的 InheritableThreadLocal 類型變量的值,但是不能獲取到 ThreadLocal 類型變量的值。

    獲取不到ThreadLocal 類型的值,我們可以好理解,因為它是線程隔離的嘛。InheritableThreadLocal 是如何做到的呢?原理是什么呢?

    在Thread類中,除了成員變量threadLocals之外,還有另一個成員變量:inheritableThreadLocals。它們兩類型是一樣的:

    public class Thread implements Runnable { ThreadLocalMap threadLocals = null; ThreadLocalMap inheritableThreadLocals = null; }復(fù)制代碼

    Thread類的init方法中,有一段初始化設(shè)置:

    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ...... if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); } static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }復(fù)制代碼

    可以發(fā)現(xiàn),當(dāng)parent的inheritableThreadLocals不為null時,就會將parent的inheritableThreadLocals,賦值給前線程的inheritableThreadLocals。說白了,就是如果當(dāng)前線程的inheritableThreadLocals不為null,就從父線程哪里拷貝過來一個過來,類似于另外一個ThreadLocal,但是數(shù)據(jù)從父線程那里來的。有興趣的小伙伴們可以在去研究研究源碼~

    8. ThreadLocal的應(yīng)用場景和使用注意點

    ThreadLocal的很重要一個注意點,就是使用完,要手動調(diào)用remove()。

    而ThreadLocal的應(yīng)用場景主要有以下這幾種:

  • 使用日期工具類,當(dāng)用到SimpleDateFormat,使用ThreadLocal保證線性安全
  • 全局存儲用戶信息(用戶信息存入ThreadLocal,那么當(dāng)前線程在任何地方需要時,都可以使用)
  • 保證同一個線程,獲取的數(shù)據(jù)庫連接Connection是同一個,使用ThreadLocal來解決線程安全的問題
  • 使用MDC保存日志信息。參考與感謝
  • 徹底理解ThreadLocal
  • ThreadLocal是如何導(dǎo)致內(nèi)存泄漏的
  • 深入分析 ThreadLocal 內(nèi)存泄漏問題

    作者:撿田螺的小男孩鏈接:http://juejin.cn/post/7126708538440679460

  • 歡迎分享轉(zhuǎn)載→http://www.avcorse.com/read-241496.html

    Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號-5 TXT地圖HTML地圖XML地圖