• 玩碎Java之ThreadLocal的原理


    ThreadLocal,我们依然从一个例子看起。

    例子

    public class ThreadLocalTest2 {
    
        public static void main(String[] args) {
            // ①,强引用tl1
            ThreadLocal tl1 = new ThreadLocal();
            tl1.set(11);
            System.gc();
            tl1.get();
            System.gc();
            // tl1.remove();
    
            // ②,没有强引用
            new ThreadLocal<>().set(22);
            System.gc();
    
            // ③,方法里的tl
            ThreadLocalTest2 test2 = new ThreadLocalTest2();
            test2.test1();
            System.gc();
    
            Thread thread = Thread.currentThread();
            System.out.println(thread);
    		
        }
    
        void test1() {
            ThreadLocal<Integer> tl = new ThreadLocal<>();
            tl.set(33);
            // tl.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    debug模式下,我们看下thread里ThreadLocalMap里的情况,如下图。
    在这里插入图片描述
    ①因为存在强引用,所以对应的referent不为null。
    ②没有强引用,垃圾回收后,referent为null。
    ③ThreadLocal仅在test1()方法中有效,所以垃圾回收后,referent为null。

    ThreadLocal源码解析

    ThreadLocal类结构

    public class ThreadLocal<T> {
    	...
    	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;
    			}
    		}
    		...	
    	}
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从线程到ThreadLocal.set(value)的value的引用关系就是这样的

    Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
    
    • 1

    set(T value)

    设置线程本地变量的内容。

    public void set(T value) {
    	Thread t = Thread.currentThread();
    	ThreadLocalMap map = getMap(t);
    	if (map != null)
    		map.set(this, value);
    	else
    		createMap(t, value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    void createMap(Thread t, T firstValue) {
    	t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    • 1
    • 2
    • 3

    get()

    获取线程本地变量的value值

    public T get() {
    	Thread t = Thread.currentThread();
    	ThreadLocalMap map = getMap(t);
    	if (map != null) {
    		ThreadLocalMap.Entry e = map.getEntry(this);
    		if (e != null) {
    			@SuppressWarnings("unchecked")
    			T result = (T)e.value;
    			return result;
    		}
    	}
    	return setInitialValue();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • initialValue()
      设置 ThreadLocal 的初始化值,未 set(T value) 初次获取 Thread 对应的 Value 值时会调用,即被 setInitialValue 方法调用。需要重写该方法。
    protected T initialValue() {
    	return null;
    }
    
    • 1
    • 2
    • 3

    remove()

    移除线程本地变量。

    public void remove() {
    	ThreadLocalMap m = getMap(Thread.currentThread());
    	if (m != null)
    		m.remove(this);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ThreadLocal使用完后,一定要调用remove()!!!

    不手动调用remove()方法,
    1.容易发生内存泄漏。
    2.线程池场景下后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

    几个问题

    ThreadLocalMap的KEY为什么设计成弱引用

    • 如果设计成强引用,即使我们使用完ThreadLocal,只要线程没有结束,线程对应的ThreadLocal就不会被回收。这个时候当ThreadLocal太多的时候就会出现内存泄漏的问题。
    • 设计为弱引用后,只要没有了强引用,jvm垃圾回收时,便会将key(ThreadLocal)进行回收。
      降低内存泄漏的风险。

    ThreadLocalMap的VALUE为什么不设计成弱引用

    这里也用一个例子,来说明value为什么不是弱引用

    public class TLLeakTest {
        public static void main(String[] args) {
    
            // ①,key和value均为WeakReference
            Map<WeakReference<Integer>, WeakReference<Integer>> map = new HashMap<>(8);
            WeakReference<Integer> key = new WeakReference<>(1);
            WeakReference<Integer> value = new WeakReference<>(777);
            map.put(key,value);
            System.gc();
            System.out.println("get1: " + map.get(key).get());
    
    
            // ②,key为WeakReference,value为HardReference
            Map<WeakReference<Integer>, Integer> map2 = new HashMap<>(8);
            WeakReference<Integer> key2 = new WeakReference<>(1);
            map2.put(key2, 999);
            System.gc();
            System.out.println("get2: "+ map2.get(key2));
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    get1: null
    get2: 999
    
    • 1
    • 2

    以上结果,可以说明为什么value不能为弱引用。
    如果value也为弱引用,那么得到的结果会为null。

    ThreadLocal为什么内存泄露

    使用完ThreadLocal,如果没有及时remove(),那么key因为是弱引用就会被回收,但value是强引用,value没被回收,导致value永远存在,value越来越多后,造成内存泄漏。

  • 相关阅读:
    手写迷你Spring框架
    模型训练出现NAN
    python的输入input()和输出print(),及经验用法
    REST 服务
    OSEK network management
    课堂练习11 面向对象编程
    GDB使用技巧
    电视盒子什么牌子好?花费30天测评盘点超值电视盒子推荐
    Python:实现miller rabin米勒-拉宾素性检验算法(附完整源码)
    【新书推荐】AI时代,当程序员遇到ChatGPT,开发效率飞起来!
  • 原文地址:https://blog.csdn.net/yuan882696yan/article/details/125471911