硬核剖析ThreadLocal源碼,面試官看了直呼內行

工作面試中經常遇到ThreadLocal,但是很多同學并不了解ThreadLocal實現原理,到底為什么會發生內存泄漏也是一知半解?今天一燈帶你深入剖析ThreadLocal源碼,總結ThreadLocal使用規范,解析ThreadLocal高頻面試題 。
1. ThreadLocal是什么ThreadLocal是線程本地變量,就是線程的私有變量,不同線程之間相互隔離 , 無法共享,相當于每個線程拷貝了一份變量的副本 。
目的就是在多線程環境中,無需加鎖,也能保證數據的安全性 。
2. ThreadLocal的使用/** * @author 一燈架構 * @apiNote ThreadLocal示例 **/public class ThreadLocalDemo {// 1. 創建ThreadLocalstatic ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 2. 給ThreadLocal賦值threadLocal.set("關注公眾號:一燈架構");// 3. 從ThreadLocal中取值String result = threadLocal.get();System.out.println(result); // 輸出 關注公眾號:一燈架構// 4. 刪除ThreadLocal中的數據threadLocal.remove();System.out.println(threadLocal.get()); // 輸出null}}ThreadLocal的用法非常簡單 , 創建ThreadLocal的時候指定泛型類型,然后就是賦值、取值、刪除值的操作 。
不同線程之間 , ThreadLocal數據是隔離的,測試一下:
/** * @author 一燈架構 * @apiNote ThreadLocal示例 **/public class ThreadLocalDemo {// 1. 創建ThreadLocalstatic ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {IntStream.range(0, 5).forEach(i -> {// 創建5個線程,分別給threadLocal賦值、取值new Thread(() -> {// 2. 給ThreadLocal賦值threadLocal.set(i);// 3. 從ThreadLocal中取值System.out.println(Thread.currentThread().getName()+ "," + threadLocal.get());}).start();});}}輸出結果:
Thread-2,2Thread-4,4Thread-1,1Thread-0,0Thread-3,3可以看出不同線程之間的ThreadLocal數據相互隔離,互不影響 , 這樣的實現效果有哪些應用場景呢?
3. ThreadLocal應用場景ThreadLocal的應用場景主要分為兩類:

  1. 避免對象在方法之間層層傳遞,打破層次間約束 。
    比如用戶信息,在很多地方都需要用到,層層往下傳遞,比較麻煩 。這時候就可以把用戶信息放到ThreadLocal中 , 需要的地方可以直接使用 。
  2. 拷貝對象副本 , 減少初始化操作,并保證數據安全 。
    比如數據庫連接、Spring事務管理、SimpleDataFormat格式化日期,都是使用的ThreadLocal,即避免每個線程都初始化一個對象 , 又保證了多線程下的數據安全 。
使用ThreadLocal保證SimpleDataFormat格式化日期的線程安全,代碼類似下面這樣:
/** * @author 一燈架構 * @apiNote ThreadLocal示例 **/public class ThreadLocalDemo {// 1. 創建ThreadLocalstatic ThreadLocal<SimpleDateFormat> threadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static void main(String[] args) {IntStream.range(0, 5).forEach(i -> {// 創建5個線程,分別從threadLocal取出SimpleDateFormat,然后格式化日期new Thread(() -> {try {System.out.println(threadLocal.get().parse("2022-11-11 00:00:00"));} catch (ParseException e) {throw new RuntimeException(e);}}).start();});}}4. ThreadLocal實現原理ThreadLocal底層使用ThreadLocalMap存儲數據,而ThreadLocalMap內部是一個數組,數組里面存儲的是Entry對象,Entry對象里面使用key-value存儲數據,key是ThreadLocal實例對象本身,value是ThreadLocal的泛型對象值 。
硬核剖析ThreadLocal源碼,面試官看了直呼內行

文章插圖
4.1 ThreadLocalMap源碼static class ThreadLocalMap {// Entry對象 , WeakReference是弱引用,當沒有引用指向時,會被GC回收static class Entry extends WeakReference<ThreadLocal<?>> {// ThreadLocal泛型對象值Object value;// 構造方法,傳參是key-value// key是ThreadLocal對象實例,value是ThreadLocal泛型對象值Entry(ThreadLocal<?> k, Object v) {super(k);value = https://www.huyubaike.com/biancheng/v;}}// Entry數組,用來存儲ThreadLocal數據private Entry[] table;// 數組的默認容量大小private static final int INITIAL_CAPACITY = 16;// 擴容的閾值,默認是數組大小的三分之二private int threshold;private void setThreshold(int len) {threshold = len * 2 / 3;}}4.2 set方法源碼// 給ThreadLocal設值public void set(T value) {// 獲取當前線程對象Thread t = Thread.currentThread();// 獲取此線程對象中的ThreadLocalMap對象ThreadLocalMap map = getMap(t);// 如果ThreadLocal已經設過值,直接設值,否則初始化if (map != null)// 設值的key就是當前ThreadLocal對象實例 , value是ThreadLocal泛型對象值map.set(this, value);else// 初始化ThreadLocalMapcreateMap(t, value);}

推薦閱讀