探索ThreadLocal

java

特色

  • ThreadLocal是一個線程內部的變量,只在本線程中使用,隔離其餘線程
  • ThreadLocal內部維護了一個ThreadLocalMap
  • Thread內部引用了ThreadLocalMap
  • ThreadLocalMap能夠保存鍵值對,可是一個ThreadLocal只能保存一個值,而且各個線程數據互不干擾
  • ThreadLocalMap存儲時的key永遠爲當前的ThreadLocal
  • ThreadLocalMap存儲時的key弱引用

ThreadLocalMap

每一個ThreadLocal只能存儲一個數據,若是須要存儲多個值的話,能夠定義多個ThreadLocalThreadLocal在內部維護了一個ThreadLocalMap用來存儲這些值。web

ThreadLocalMap並無去實現Map接口,它定義了一個Entry數組,每一個Entry<key,value>的形式來保存值,其中key爲當前ThreadLocal自己,value爲要保存的值。數組

注意Entry繼承了WeakReference,它的key弱引用的,會被垃圾回收掉,因此會存在keynull的狀況安全

ThreadLocalMap提供了三個方法:微信

  • set():以當前ThreadLocalkey存放值
  • get():以當前ThreadLocalkey獲取存放的值
  • remove():清除數據

set()方法

  • 獲取當前線程Thread.currentThread()
  • 獲取當前線程的ThreadLocalMap
  • 判斷ThreadLocalMap是否存在
  • 不存在的,經過createMap,初始化一個ThreadLocalMap,並賦值
  • 存在的,將當前ThreadLocal做爲key,進行插入操做:
    • 經過ThreadLocal的哈希值獲取要插入的位置
    • 若是當前位置的Entry爲空,直接在該位置初始化一個Entry對象來實現插入操做;
    • 若是當前位置Entry的key和要設置的key相同,則覆蓋原來的value
    • 若是當前位置Entry的key爲null:
      • 循環獲取下一位置
      • 若是key和要設置的key相同,則覆蓋這個位置的值,並將這個位置和要插入點的entry互換,清理key爲null的值;
      • 若是當前位置的Entry爲null,退出循環,並在當前位置生成一個新的entry,並清理key爲null的值;

get()方法

  • 獲取當前線程Thread.currentThread()
  • 從當前線程獲取ThreadLocalMap
  • 判斷ThreadLocalMap是否存在
  • 不存在的調用setInitialValue進行初始化,並返回null;
  • 存在的,則以當前ThreadLocal做爲key獲取值:
    • 經過ThreadLocal的哈希值獲取要獲取值的位置
    • 當前位置的entry存在且key相同的,直接返回當前值;
    • 當前位置的entry存在且當前key爲null的,執行清理重置方法
    • 循環獲取下一位置的entry進行對比,若是下一位置的key相同,則返回該值;
    • 若是下一位置的entry爲null,則說明該值不存在退出循環返回null;

remove()方法

清理當前ThreadLocal對應的Entry對象。並調用清理重置方法app

清理重置方法

  • 處理區間:Entry數組當前位置到下一個不爲null的Entry之間的數據;
  • 清理key爲null的Entry:value設爲null,Entry設爲null;
  • 重置key不爲null的Entry:
    • 經過key的哈希值獲取在Entry的數組的索引h;
    • 若是h和當前Entry的索引不一致進行位置重置:
      • 將當前位置的Entry設爲null
      • 從h處開始日後找到首個Entry爲null的位置
      • 將找到的新位置處的Entry設爲原來的entry

Hash衝突

ThreadLocalMap並無實現Map接口,它不是經過鏈表的形式去避免Hash衝突的,而是經過後移的方式去實現。set方法時,若是當前要存放的位置的key和要設置的key不一致,則會對下一個位置進行判斷,直到找到key相同或者爲null或者Entrynull的位置。工具

內存泄漏問題

在實際的項目中,咱們的線程通常都是由線程池來管理的,線程會一直存在,ThreadLocalMap的value就有可能得不到回收,發送內存泄漏。爲了處理這一問題,ThreadLocal的get()、set()方法都有可能會清除keynullEntry對象。安全起見,當咱們使用完後應該手動調用remove()方法清理掉數據。spa

用途

全局變量

某些數據好比用戶ID,極可能在整條業務線上多個方法中都須要用到,若是經過方法參數的形式一層一層的傳遞下去,總體代碼顯得凌亂不優雅,這時能夠經過ThreadLocal的方式存儲。一般能夠經過AOP或者攔截器的方式進行賦值,執行完業務邏輯以後調用remove()方法。線程

private final static ThreadLocal<UserInfo> TL_USER = new ThreadLocal<>();

TL_USER.set(userInfo);

UserInfo userInfo = TL_USER.get();

TL_USER.remove();
複製代碼

獨享對象

在實際項目中咱們一般會將時間相關的方法寫在一個工具類中,每每會用到SimpleDateFormat進行格式化,它是線程不安全的。能夠經過ThreadLocal來實現獨享對象code

private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
複製代碼

平常求贊

創做不易,若是各位以爲有幫助,求點贊 支持


求關注

微信公衆號: 俞大仙

相關文章
相關標籤/搜索