栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

玩碎Java之ThreadLocal的原理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

玩碎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 tl = new ThreadLocal<>();
        tl.set(33);
        // tl.remove();
    }
}

debug模式下,我们看下thread里ThreadLocalMap里的情况,如下图。

①因为存在强引用,所以对应的referent不为null。
②没有强引用,垃圾回收后,referent为null。
③ThreadLocal仅在test1()方法中有效,所以垃圾回收后,referent为null。

ThreadLocal源码解析 ThreadLocal类结构
public class ThreadLocal {
	...
	static class ThreadLocalMap {
		
		static class Entry extends WeakReference> {
			
			Object value;

			Entry(ThreadLocal k, Object v) {
				super(k);
				value = v;
			}
		}
		...	
	}
	...
}

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

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
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);
}
void createMap(Thread t, T firstValue) {
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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();
}
  • initialValue()
    设置 ThreadLocal 的初始化值,未 set(T value) 初次获取 Thread 对应的 Value 值时会调用,即被 setInitialValue 方法调用。需要重写该方法。
protected T initialValue() {
	return null;
}
remove()

移除线程本地变量。

public void remove() {
	ThreadLocalMap m = getMap(Thread.currentThread());
	if (m != null)
		m.remove(this);
}

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> map = new HashMap<>(8);
        WeakReference key = new WeakReference<>(1);
        WeakReference value = new WeakReference<>(777);
        map.put(key,value);
        System.gc();
        System.out.println("get1: " + map.get(key).get());


        // ②,key为WeakReference,value为HardReference
        Map, Integer> map2 = new HashMap<>(8);
        WeakReference key2 = new WeakReference<>(1);
        map2.put(key2, 999);
        System.gc();
        System.out.println("get2: "+ map2.get(key2));

    }
}
get1: null
get2: 999

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

ThreadLocal为什么内存泄露

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/996036.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号