ThreadLocal实现原理分析

一. ThreadLocal是什么

ThreadLocal 可以理解为线程本地变量,每个线程拥有本地变量的副本,各个线程之间的变量互不干扰,主要用于保证多线程环境下变量的安全。

也就是对于同一个 ThreadLocal,每个线程通过 get、set、remove 接口操作只会影响自身线程的数据,不会干扰其他线程中的数据。

二. ThreadLocal的数据结构

ThreadLocal 的数据结构如下图所示:
在这里插入图片描述
可以看出,每个线程都拥有自己的一个 ThreadLocalMap,它是 ThreadLocal 中的内部类,使用数组来存储数据,数组存放的元素类型为 Entry。Thread 类中包含的成员变量 threadLocals 就是 ThreadLocalMap 类型的,类图如下所示:
在这里插入图片描述
可以看出,ThreadLocalMap 没有实现 Map 接口,是用独立的方式实现了 Map 的功能,其内部的 Entry 也是独立实现的,代码如下:

static class Entry extends WeakReference<ThreadLocal> {
    
    
    /** The value associated with this ThreadLocal. */
    Object value;

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

Entry 继承自 WeakReference(弱引用,生命周期只能存活到下次GC前),但只有 Key 是弱引用类型的,Value 并非弱引用。

可以简单理解成 ThreadLocalMap 的 Key 是 ThreadLocal 变量,Value 是线程本地的数据。

三. ThreadLocal核心方法解析

ThreadLocal 类提供如下几个核心方法:

//用于获取当前线程的副本变量值
public T get()

//用于保存当前线程的副本变量值
public void set(T value)

//移除当前线程的副本变量值
public void remove()

get() 方法实现:

public T get() {
    
    
	// 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    
    
    	// 根据当前ThreadLocal对象应用获取Entry,存在则直接返回value值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    // 如果threadLocals为空,初始化当前线程threadLocals的变量
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    
    
    return t.threadLocals;
}
// threadLocals存在,设置初始值;不存在,初始化threadLocals变量
private T setInitialValue() {
    
    
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    
    
    return null;
}

set() 方法实现:

public void set(T value) {
    
    
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程自身的threadLocals变量
    ThreadLocalMap map = getMap(t);
    if (map != null) 
        // map不为空,则设置
        map.set(this, value);
    else 
        // map为空,说明第一次调用,初始化线程的threadLocals变量
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    
    
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    
    
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

remove() 方法实现:

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

四. 问题分析

1.ThreadLocalMap 里 Entry 为何声明为 WeakReference?

因为如果这里使用普通的 key-value 形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。

弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个 ThreadLocal 已经没有强引用可达,则随着它被垃圾回收,在 ThreadLocalMap 里对应的 Entry 的键值会失效,这为 ThreadLocalMap 本身的垃圾清理提供了便利。

2.什么情况下会发生内存泄漏?

发生 GC 时弱引用 Key会被回收,而 Value 不会回收,如果创建 ThreadLocal 的线程一直持续运行,并且一直不执行get、set、remove方法,这些 Key 为 null 的 Entry 的 Value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> Value,那么这个Entry对象中的 Value 就有可能一直得不到回收,从而发生内存泄漏。

3.如何避免内存泄漏?

既然 Key 是弱引用,那么我们要做的事,就是在调用 ThreadLocal 的 get()、set() 方法完成后及时调用 remove() 方法,将 Entry 节点和 Map 的引用关系移除,这样整个 Entry 对象在 GC Roots 分析后就变成不可达了,下次 GC 的时候就可以被回收了。

おすすめ

転載: blog.csdn.net/j1231230/article/details/116193336