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

Java 面試高頻 ThreadLocal

開發 前端
ThreadLocalMap 是一個自定義map,它并沒有實現Map接口,而且他的Entry是繼承WeakReference(弱引用)的,也沒有看到HashMap中的next,所以不存在鏈表了。

面試題

  • ThreadLocal中ThreadLocalMap的數據結構和關系?
  • ThreadLocal的key是弱引用,這是為什么?
  • ThreadLocal內存泄露問題你知道嗎?
  • ThreadLocal中最后為什么要加remove方法?

是什么? 能干嘛

ThreadLocal提供線程局部變量。這些變量與正常的變量不同,因為每一個線程在訪問ThreadLocal實例的時候(通過其get或set方法)都有自己的、獨立初始化的變量副本。ThreadLocal實例通常是類中的私有靜態字段,使用它的目的是希望將狀態(例如,用戶ID或事務ID)與線程關聯起來。

主要解決了讓每個線程綁定自己的值,通過使用get()和set()方法,獲取默認值或將其值更改為當前線程所存的副本的值從而避免了線程安全問題。

一句話如何才能不爭搶

  1. 加入synchronized或者Lock控制資源的訪問順序
  2. 人手一份,大家各自安好,沒必要搶奪

舉個栗子-阿里規范

為什么SimpleDateFormat是線程不安全的?

官方文檔說明

測試

public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 模擬并發環境下使用SimpleDateFormat的parse方法將字符串轉換成Date對象
     * @param stringDate
     * @return
     * @throws Exception
     */
    public static Date parseDate(String stringDate)throws Exception {
        return sdf.parse(stringDate);
    }

    public static void main(String[] args) throws Exception {
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                try {
                    System.out.println(DateUtils.parseDate("2020-11-11 11:11:11"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }

源碼分析

SimpleDateFormat類內部有一個Calendar對象引用,它用來儲存和這個SimpleDateFormat相關的日期信息。

例如sdf.parse(dateStr),sdf.format(date) 諸如此類的方法參數傳入的日期相關String,Date等等, 都是交由Calendar引用來儲存的。這樣就會導致一個問題:如果你的SimpleDateFormat是個static的, 那么多個thread 之間就會共享這個SimpleDateFormat, 同時也是共享這個Calendar引用。

SimpleDataFormat的parse()方法會先調用Calendar.clear(),然后調用Calendar.add(),如果一個線程先調用了add()然后另一個線程又調用了clear(),這時候parse()方法解析的時間就不對了。

舉個例子:

假設線程 A 剛執行完 calendar.setTime(date) 語句,把時間設置為 2020-09-01,但線程還沒執行完,線程 B 又執行了 calendar.setTime(date) 語句,把時間設置為 2020-09-02,這個時候就出現幻讀了,線程 A 繼續執行下去的時候,拿到的 calendar.getTime 得到的時間就是線程B改過之后的。

如何解決

  • 解決1

將SimpleDateFormat定義成局部變量。

缺點:每調用一次方法就會創建一個SimpleDateFormat對象,方法結束又要作為垃圾回收。

public static void main(String[] args) throws Exception {
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                try {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    //System.out.println(DateUtils.parseDate("2020-11-11 11:11:11"));
                    System.out.println(sdf.parse("2020-11-11 11:11:11"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
  • 解決2

ThreadLocal,也叫做線程本地變量或者線程本地存儲

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

    /**
     * ThreadLocal可以確保每個線程都可以得到各自單獨的一個SimpleDateFormat的對象,那么自然也就不存在競爭問題了。
     * @param stringDate
     * @return
     * @throws Exception
     */
    public static Date parseDateTL(String stringDate)throws Exception {
        return sdf_threadLocal.get().parse(stringDate);
    }

    public static void main(String[] args) throws Exception {
        for (int i = 1; i < 30; i++) {
            new Thread(() -> {
                try {
                    System.out.println(DateUtils.parseDateTL("2020-11-11 11:11:11"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
  • 其它

加鎖

第3方時間庫

ThreadLocal Thread ThreadLocalMap 之間的關系

ThreadLocal :每個線程通過此對象都會返回各自的值,互不干擾,這是因為每個線程都存著自己的一份副本。需要注意的是線程結束后,它所保存的所有副本都將進行垃圾回收(除非存在對這些副本的其他引用)

ThreadLocal的get操作是這樣執行的:ThreadLocalMap map = thread.threadLocals -> return map.getEntry(threadLocal)ThreadLocal的set操作是這樣執行的:ThreadLocalMap map = thread.threadLocals -> map.set(threadLocal, value)

三者的關系是:

  • 每個Thread對應的所有ThreadLocal副本都存放在ThreadLocalMap對象中,key是ThreadLocal,value是副本數據。
  • ThreadLocalMap對象存放在Thread對象中。
  • 通過ThreadLocal獲取副本數據時,實際是通過訪問Thread來獲取ThreadLocalMap,再通過ThreadLocalMap獲取副本數據。

ThreadLocal內存泄露問題

阿里手冊

什么是內存泄漏

不再會被使用的對象或者變量占用的內存不能被回收,就是內存泄露。

ThreadLocal在保存的時候會把自己當做Key存在ThreadLocalMap中,正常情況應該是key和value都應該被外界強引用才對,但是現在key被設計成WeakReference弱引用了。

強引用、軟引用、虛引用、弱引用

強引用

當內存不足,jvm開始垃圾回收,對于強引用的對象,就算是出現了OOM也不會對該對象進行回收。這也是Java中最常見的普通對象的引用,只要還有強引用指向這個對象,就不會被垃圾回收。當這個對象沒有了其他的引用關系,只要是超過了引用的作用域,或者顯示的將強引用賦值為null,一般就可以進行垃圾回收了。

軟引用

軟引用是相對強引用弱化了一些的引用,對于軟引用的對象來說:

  • 當內存充足時,它不會被回收。
  • 當內存不足時。會被回收。

通常用在對內存敏感的程序中,就像高速緩存。

弱引用

發現即回收

弱引用也是用來描述那些非必需對象,被弱引用關聯的對象只能生存到下一次垃圾收集發生為止。在系統GC時,只要發現弱引用,不管系統堆空間使用是否充足,都會回收掉只被弱引用關聯的對象。

但是,由于垃圾回收器的線程通常優先級很低,因此,并不一定能很快地發現持有弱引用的對象。在這種情況下,弱引用對象可以存在較長的時間。弱引用和軟引用一樣,在構造弱引用時,也可以指定一個引用隊列,當弱引用對象被回收時,就會加入指定的引用隊列,通過這個隊列可以跟蹤對象的回收情況。軟引用、弱引用都非常適合來保存那些可有可無的緩存數據。如果這么做,當系統內存不足時,這些緩存數據會被回收,不會導致內存溢出。而當內存資源充足時,這些緩存數據又可以存在相當長的時間,從而起到加速系統的作用。在JDK1.2版之后提供了WeakReference類來實現弱引用

// 聲明強引用
Object obj = new Object();
// 創建一個弱引用
WeakReference<Object> sf = new WeakReference<>(obj);
obj = null; //銷毀強引用,這是必須的,不然會存在強引用和弱引用

虛引用

也稱為“幽靈引用”或者“幻影引用”,是所有引用類型中最弱的一個

一個對象是否有虛引用的存在,完全不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它和沒有引用幾乎是一樣的,隨時都可能被垃圾回收器回收。它不能單獨使用,也無法通過虛引用來獲取被引用的對象。當試圖通過虛引用的get()方法取得對象時,總是null。為一個對象設置虛引用關聯的唯一目的在于跟蹤垃圾回收過程。比如:能在這個對象被收集器回收時收到一個系統通知。

虛引用必須和引用隊列一起使用。虛引用在創建時必須提供一個引用隊列作為參數。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象后,將這個虛引用加入引用隊列,以通知應用程序對象的回收情況。由于虛引用可以跟蹤對象的回收時間,因此,也可以將一些資源釋放操作放置在虛引用中執行和記錄。在JDK1.2版之后提供了PhantomReference類來實現虛引用。

// 聲明強引用
Object obj = new Object();
// 聲明引用隊列
ReferenceQueue phantomQueue = new ReferenceQueue();
// 聲明虛引用(還需要傳入引用隊列)
PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
obj = null;

內存泄漏問題

回到ThreadLocal這邊來說:ThreadLocal在沒有外部強引用時,發生GC時會被回收,如果創建ThreadLocal的線程一直持續運行,那么這個Entry對象中的value就有可能一直得不到回收,發生內存泄露。

就比如線程池里面的線程,線程都是復用的,那么之前的線程實例處理完之后,出于復用的目的線程依然存活,所以,ThreadLocal設定的value值被持有,導致內存泄露。

按照道理一個線程使用完,ThreadLocalMap是應該要被清空的,但是現在線程被復用了。

那怎么解決?

在代碼的最后使用remove就好了,我們只要記得在使用的最后用remove把值清空就好了。

ThreadLocal<String> localName = new ThreadLocal();
try {
    localName.set("張三");
    ……
} finally {
    localName.remove();
}

remove的源碼很簡單,找到對應的值全部置空,這樣在垃圾回收器回收的時候,會自動把他們回收掉。

那為什么ThreadLocalMap的key要設計成弱引用?

key不設置成弱引用的話就會造成和entry中value一樣內存泄漏的場景。

補充一點:ThreadLocal的不足,我覺得可以通過看看netty的fastThreadLocal來彌補,大家有興趣可以康康。

key為null的entry的值傳遞的bug

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用引用他,那么系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話(比如正好用在線程池),這些key為null的Entry的value就會一直存在一條強引用鏈。

雖然弱引用,保證了key指向的ThreadLocal對象能被及時回收,但是v指向的value對象是需要ThreadLocalMap調用get、set時發現key為null時才會去回收整個entry、value,因此弱引用不能100%保證內存不泄露。我們要在不使用某個ThreadLocal對象后,手動調用remoev方法來刪除它,

另外在線程池中,不僅僅是內存泄露的問題,因為線程池中的線程是重復使用的,意味著這個線程的ThreadLocalMap對象也是重復使用的,如果我們不手動調用remove方法,那么后面的線程就有可能獲取到上個線程遺留下來的value值,造成bug。--值傳遞的問題

值傳遞的問題

ThreadLocal 無法共享線程中的數據,InheritableThreadLocal 可以再父子線程之間完成值的傳遞

InheritableThreadLocal中createMap,以及getMap方法處理的對象不一樣了,其中在ThreadLocal中處理的是threadLocals,而InheritableThreadLocal中的是inheritableThreadLocals。Inheritablethreadlocal在父子線程傳遞的時候,依托的是thread的,在執行init( )初始化方法。在init方法中會執行parent.inheritableThreadLocals,這個方法會把父線程的值賦給子線程,如果我們使用的是線程池,這樣不會再重新執行init()初始化方法,而是直接使用已經創建過的線程,所以這里的值不會二次產生變化,做不到真正的父子線程數據傳遞,就會出現數據錯亂的問題。

TransmittableThreadLocal 的原理:它底層使用的TtlRunnable, 在TtlRunnable 構造方法中,會獲取當前線程中所有的上下文,并儲存在 AtomicReference 中。而且TtlRunnable 是實現于 Runnable,在 TtlRunnable run 方法中會執行 Runnable run 方法。當線程執行時,TtlRunnable 會從 AtomicReference 中獲取出調用線程中所有的上下文,并把上下文通過 TransmittableThreadLocal.Transmitter.replay 方法把上下文復制到當前線程。這樣就可以在使用線程池的情況下也可以完成值的傳遞了。

ThreadLocalMap的原理

介紹

ThreadLocalMap 是一個自定義map,它并沒有實現Map接口,而且他的Entry是繼承WeakReference(弱引用)的,也沒有看到HashMap中的next,所以不存在鏈表了。

為什么底層用數組?沒有了鏈表怎么解決Hash沖突呢?

用數組是因為,我們開發過程中可以一個線程可以有多個TreadLocal來存放不同類型的對象的,但是他們都將放到你當前線程的ThreadLocalMap里,所以肯定要數組來存。

從源碼里面看到ThreadLocalMap在存儲的時候會給每一個ThreadLocal對象一個threadLocalHashCode,在插入過程中,根據ThreadLocal對象的hash值,定位到table中的位置i,

int i = key.threadLocalHashCode & (len-1)。然后會判斷一下:如果當前位置是空的,就初始化一個Entry對象放在位置i上;如果位置i不為空,如果這個Entry對象的key正好是即將設置的key,那么就刷新Entry中的value;如果位置i的不為空,而且key不等于entry,那就找下一個空位置,直到為空為止。

小總結

  • ThreadLocal 并不解決線程間共享數據的問題
  • ThreadLocal 適用于變量在線程間隔離且在方法間共享的場景
  • ThreadLocal 通過隱式的在不同線程內創建獨立實例副本避免了實例線程安全的問題
  • 每個線程持有一個只屬于自己的專屬Map并維護了ThreadLocal對象與具體實例的映射,該Map由于只被持有它的線程訪問,故不存在線程安全以及鎖的問題
  • ThreadLocalMap的Entry對ThreadLocal的引用為弱引用,避免了ThreadLocal對象無法被回收的問題
責任編輯:武曉燕 來源: 今日頭條
相關推薦

2020-07-28 08:59:22

JavahreadLocal面試

2021-02-23 12:43:39

Redis面試題緩存

2023-02-28 11:27:50

線程處理解決共享變量

2025-03-28 08:53:51

2019-12-26 09:52:33

Redis集群線程

2025-01-14 11:21:35

2022-05-11 07:36:12

Java線程安全

2015-09-09 08:45:49

JavaThreadLocal

2023-10-07 08:40:57

緩存屬性Spring

2021-01-22 11:58:30

MySQL數據庫開發

2024-10-28 08:15:32

2022-07-26 08:07:03

Python淺拷貝深拷貝

2024-11-11 10:40:19

Java變量副本

2022-10-25 10:20:31

線程變量原理

2021-08-05 05:04:50

熱部署模型字節

2024-09-24 10:28:22

2018-04-09 08:17:36

線程ThreadLocal數據

2022-08-22 18:57:29

React前端面試

2025-04-01 08:25:00

OSPF網絡IT

2022-11-04 08:47:52

底層算法數據
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产一区二区在线免费播放 | 日本一区二区三区四区 | 久久久久久亚洲精品 | 国产综合久久久久久鬼色 | 国产精品一区二区三区在线 | 久久久国产精品入口麻豆 | 香蕉视频91 | 日韩精品一区二区三区中文在线 | 国产一级片免费视频 | 欧美日韩精品专区 | 欧美一级小视频 | 精品国产精品 | 免费观看av | 久久精品视频网站 | 祝你幸福电影在线观看 | 国产精品永久 | 91久久精品国产91久久 | 久久精品亚洲国产奇米99 | 久久99蜜桃综合影院免费观看 | 天天av天天好逼 | 一区二区电影网 | 欧美精品一区二区三区四区五区 | 成人小视频在线 | 成人亚洲性情网站www在线观看 | 国产一区二区在线免费观看 | 久久精品一区二区 | 成人精品一区二区三区中文字幕 | 日韩精品一区中文字幕 | 国产精品有限公司 | 色精品| 精品无码久久久久久国产 | 九九亚洲 | 中文字幕视频在线观看 | 色资源在线 | 亚洲精品乱码久久久久久蜜桃91 | 91原创视频 | 日本三级在线 | 丁香六月激情 | 亚洲网站免费看 | 日韩一区二区三区av | 日本精品久久久久久久 |