ThreadLocal源码解析

256 阅读3分钟

1 ThreadLocal是什么

ThreadLocal 位于 java.lang 包下,是一个关于创建线程局部变量的类。也就是说这个变量的作用域是线程,其他线程访问不了。 通常我们创建的变量是可以被任何一个线程访问的,而使用 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法访问。很抽象哈,看看我下面的应用场景

应用场景:当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,比如说Looper,每一个线程对应自己的Looper. 描述起来感觉还是有点抽象,不如设想一下如果自己实现上述的每个线程与Looper一一对应这个功能需要怎么做?我想到的办法就是自己创建一个线程安全的Map<Thread, Value>来维护数据的增、删、改、查。所以直接用ThreadLocal就可以直接实现不用自己维护,感觉还是非常方便的。这就是ThreadLocal存在的意义与使用场景,够直白了吧

2 ThreadLocal怎么使用

可以参考Looper.java的用法

// 创建一个静态的ThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
    // 查询本线程的Looper是否被实例化
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 实例化Looper,并放入ThreadLocal实例中与本线程产生关联
    sThreadLocal.set(new Looper(quitAllowed));
}

3 ThreadLocal主要方法分析

ThreadLocal暴露的public方法主要是 get/set/remove

3.1 get

获取ThreadLocal实例对应的Value

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取线程的变量t.threadLocals值,也就是每个线程单独存储的ThreadLocalMap
    ThreadLocalMap map = getMap(t);// 1
    // 判空处理与通过key(ThreadLocal)查询value
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);// 2
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // map为空则创建map,并默认设置与返回当前ThreadLocal实例对应的Value
    return setInitialValue();// 3
}

总结一下就是: 每个线程都有一个ThreadLocalMap变量,ThreadLocalMap是以ThreadLocal做为key的。所以我们设置的每一个ThreadLocal值都是通过Thread查询并添加到自己私有的map中。

1处的getMap(t)

ThreadLocalMap getMap(Thread t) {
    // 说明ThreadLocalMap是Thread的一个变量
    return t.threadLocals;
}

2处的map.getEntry(this)

// ThreadLocal实例作为key查找对应的Entry
private Entry getEntry(ThreadLocal<?> key) {
    // map操作中计算hashcode
    int i = key.threadLocalHashCode & (table.length - 1);
    // 获取hashcode对应的entry
    Entry e = table[i];
    
    // 查询这个key真的存在
    if (e != null && e.get() == key)
        return e;
    else
        // 其实就是数据结构的问题了  就不详细说了,大概就是一直用某个方法计算hashcode值找数组中对应的value
        return getEntryAfterMiss(key, i, e);
}

3处的setInitialValue()

// 在map没有创建或者ThreadLocal实例没有设置具体value的时候会走到这里
private T setInitialValue() {
    // 因为还没有value,所以此处会创建一个默认value(默认是null),所以在创建ThreadLocal的时候可以重写此方法来设置一个默认value
    T value = initialValue();
    // 获取当前线程的Thread实例
    Thread t = Thread.currentThread();
    // 获取线程的map(前面已经分析了)
    ThreadLocalMap map = getMap(t);
    
    //map判空操作  主要设计创建map与map赋值 
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

// 默认为Null
protected T initialValue() {
    return null;
}

3.2 set

设置与更新Value

public void set(T value) {
    // 获取Thread实例对象
    Thread t = Thread.currentThread();
    // 获取Thread的map数据
    ThreadLocalMap map = getMap(t);
    // 判空与添加数据   添加数据其实就是数据结构的相关内容了,可以自行查看
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

3.3 remove!!

移除数据

public void remove() {
    // 获取thread的map
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     // 进行数据移除
     m.remove(this);
}

这里涉及到一个内存泄漏的问题,比如我们不使用ThreadLocal.remove来移除数据,map中的entry持有的是ThreadLocal的弱引用,那么当线程中释放掉ThreadLocal的实例强引用以后,也就是ThreadLocal实例被gc了,map中存放的value值却没有被回收,永久存在与map中,所以存在着内存泄漏的问题。最好的做法就是在需要释放ThreadLocal的实例时先调用remove.

1814304-827baddd8b46e3ba.png

3.4 其他

4 参考