ThreadLocal之通俗易懂篇

概念

ThreadLocal字面上意思是线程本地,它的存在并不是为了来解决多线程环境的共享变量问题,而是来提供线程内部的存储空间。在多线程环境中,可以保证各个线程之间的变量相互隔离,互相独立。

初步了解ThreadLocal源码

在ThreadLocal中有四个常用的方法:1 get() 2 set() 3 initialValue(初始化) 4 remove

//下面演示的是初始化ThreadLocal的值
public class ThreadLocalTest {
	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {
			return Integer.valueOf(1);
		}
	};
}

关于ThreadLocal的源码实现,我们正常的思维就是ThreadLocal内部类维护一个线程安全的集合,比如Map。用线程ID作为Map的key,实例对象作为Map的value,这样就可以实现线程之间相互隔离的效果。但是其实这种方法现在早已经没有被沿用了

那现在采用的何种方种?我们先从get方法看起

public T get() {
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t); //这里返回当前线程的threadLocals变量
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();//如果上述map为空,就返回我们的初始化值
}

//接下来看看它的getMap方法
ThreadLocakMap getMap(Thread t){
    return t.threadLocals;
}
//上述代码的threadLocals是Thread类的成员变量,初始化为null
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是ThreadLocal的静态内部类,每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。
这样设计的好处是当Thread销毁后,对应的ThreadLocalMap也就随之销毁

初步了解ThreadLocalMap源码

static class Entry extends WerkReference<ThreadLocal<?>>{
    Object value;
    Entry(ThreadLocal<?> k,Object v){
        super(k);
        value = v;
    }
}
//从上述代码可知道,使用ThreadLocal的弱引用作为key

下面是一个引用图,实线为强引用,虚线为弱引用
ThreadLocalMap使用ThreadLocal的弱引用作为key,在虚拟机运行垃圾回收时候,这个ThreadLocal会被回收,这样ThreadLocalMap中就会出现key为null的Entry,这些key对应的value也就再无法访问,但是value中还存在一条从Current Thread过来的强引用链,只有当Current Thread销毁的时候,value才能被释放
在这里插入图片描述
那么ThreadLocalMap中如何解决这个问题?

//首先getEntry 计算存储Key的Entry的索引位置,然后判断key是否存在,不存在则调用下面的办法
//getEntryAfterMiss
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
 
    while (e != null) {
        ThreadLocal<?> k = e.get(); //用值来反查key,比较少见
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}|

若是发现key为null,说明弱引用key被回收了,这个时候会调用expungeStaleEntry(int) 在这个函数中有下面两句代码,来清理key与其对应的value。最好的方式是将ThreadLocal变量定义成private static。这样ThreadLocal的生命周期更长,一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收。

tab[staleSlot].value = null;
tab[staleSlot] = null;

个人理解:一个Thread中可以创建多个ThreadLocal用来存储不同的数据,但是他们获得的ThreadLocalMap都是同一个。多个ThreadLocal也能够创建多个Entry。

InheritableThreadLocal

InheritableThreadLocal继承自ThreadLocal,使用InheritableThreadLocal类可以使子线程继承父线程的值

扫描二维码关注公众号,回复: 10525123 查看本文章
发布了16 篇原创文章 · 获赞 51 · 访问量 6807

猜你喜欢

转载自blog.csdn.net/weixin_42683077/article/details/105004584