ThreadLocal源码解析

一、使用场景

ThreadLocal用于不同线程获取各自数据,同一个线程也可根据不同的threadlocal对象获取到各自的数据。

二、源码解析

ThreadLocal如何实现不同线程获取各自数据的呢?其实源码中使用到ThreadLocal的场景有很多,下面通过Looper来分析ThreadLocal的实现原理。

Android基于消息机制运行,每个线程都有自己的消息队列,通过Looper不断循环获取消息,进行操作。我们在不同线程中调用Looper.myLooper()可以获取到各自线程的Looper对象,我们没有传入线程的相关数据,那么是如何实现的呢?

static final ThreadLocal sThreadLocal = new ThreadLocal();
 
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

很显然,通过ThreadLocal的get方法实现,我们继续看ThreadLocal

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();
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

在ThreadLocal的get方法中首先获取了当前线程,接着获取到此线程的ThreadLocalMap对象,通过源码可以得知,它是数据存储类。好了,原来获取到的就是每个线程的数据存储类,当然可以实现不同线程获取各自的数据了。当我们获取到ThreadLocalMap之后,接下来就要取出其中的value,也就是Looper,这里有两个分支。

 if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
}

当map不为null时,当前的ThreadLocal作为key,获取value,所以(划重点)ThreadLocal不只可以实现存储不同线程的数据,也可以实现存储同一线程中不同ThreadLocal对象的数据。
当map为null时,首先获取初识value,默认是null,可以自己重写initialValue赋值,接着调用creatMap

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;
}

获取当前线程的ThreadLocalMap存储对象,并将当前ThreadLocal对象作为key,赋值初始的value。

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

调用createMap只有两个地方,set和setInitialValue,下面我们来看set

public void set(T value) {
   Thread t = Thread.currentThread();
   ThreadLocalMap map = getMap(t);
   if (map != null)
       map.set(this, value);
   else
       createMap(t, value);
}

同样,获取当前线程的ThreadLocalMap,若map不为null,则将当前ThreadLocal对象作为key,添加value,若为null,则创建ThreadLocalMap。

我们回到Looper,调用set的只有一个地方,就是prepare,主线程即UI线程会自动调用prepare,将Looper对象作为value放置到主线程的ThreadLocalMap中;所以这就是为什么子线程使用Looper时候,先手动调用prepare的原因了。

三、总结

1、在各自线程中创建其数据存储类ThreadLocalMap,并赋值给线程中的变量threadLocals;获取数据时,直接取出当前线程的threadLocals,即获得此线程的数据存储类。所以不同线程可获取到各自的数据。

2、由于ThreadLocalMap以当前ThreadLocal作为key,所以也可实现同一线程的不同ThreadLocal对象获取到各自的数据。

你可能感兴趣的:(ThreadLocal源码解析)