前言

说起 ThreadLocal,大家可能会比较陌生,但是如果想要比较好地理解 Android 的消息机制,ThreadLocal 是必须要掌握的,这是因为 Looper 的工作原理,就跟 ThreadLocal 有很大的关系,理解 ThreadLocal 的实现方式有助于我们理解 Looper 的工作原理,这篇文章就从 ThreadLocal 的用法讲起,一步一步带大家理解 ThrealLocal。

一、ThreadLocal 是什么


ThreadLocal 是一个线程内部的数据存储类,通过它可以在 指定的线程中 存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。

二、基本用法


创建,支持泛型

ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();

set 方法

mStringThreadLocal.set("developerHaoz");

get 方法

mStringThreadLocal.get();

接下来用一个完整的例子,帮助大家理解 ThreadLocal

    private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBooleanThreadLocal.set(true);
        Log.d(TAG, "Current Thread: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());
        new Thread("Thread#1"){
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "Thread 1: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());

            }
        }.start();

        new Thread("Thread#2"){
            @Override
            public void run() {
                Log.d(TAG, "Thread 2: mBooleanThrealLocal is : " + mBooleanThreadLocal.get());

            }
        }.start();
    }

在上面的代码中,在主线程中设置 mBooleanThrealLocal 的值为 true,在子线程 1 中设置为 false,在子线程 2 中不设置 mBooleanThrealLocal 的值,然后分别在 3 个线程中通过 get() 方法获取 mBooleanThrealLocal 的值

image.png
image.png

从上面的日志中可以看出,虽然在不同的线程中访问的是同一个 ThrealLocal 对象,但是它们通过 ThrealLocal 获取到的值确实不一样的,这就是 ThrealLocal 的奇妙之处了。

ThrealLocal 之所以有这么奇妙的效果,就是因为不同线程访问同一个 ThrealLocal 的 get() 方法,ThrealLocal 内部都会从各自的线程中取出一个数组,然后再从数组中根据当前 ThrealLocal 的索引去查找不同的 value 值。

三、ThrealLocal 工作原理


ThrealLocal 是一个泛型类,它的定义为 public class ThrealLocal,只要弄清楚 ThrealLocal 的 set() 和 get() 方法就可以明白它的工作原理了。

1、ThrealLocal 的 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);
    }

可以看到在 set() 方法中,先获取到当前线程,然后通过 getMap(Thread t) 方法获取一个 ThreadLocalMap,如果这个 map 不为空的话,就将 ThrealLocal 和 我们想存放的 value 设置进去,不然的话就创建一个 ThrealLocalMap 然后再进行设置。

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

ThreadLocalMap 其实是 ThreadLocal 里面的静态内部类,而每一个 Thread 都有一个对应的 ThrealLocalMap,因此获取当前线程的 ThrealLocal 数据就变得异常简单了。

public class Thread implements Runnable {

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

下面看一下,ThrealLocal 的值到底是如何在 threadLocals 中进行存储的。在 threadLocals 内部有一个数组,private Entry[] table,ThrealLocal 的值就存在这个 table 数组中。

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal> {
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        private Entry[] table;
}

2、ThrealLocal 的 get() 方法

上面分析了 ThreadLocal 的 set() 方法,这里分析它的 get() 方法,代码如下

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

可以发现,ThrealLocal 的 get() 方法的逻辑也比较清晰,它同样是取出当前线程的 threadLocals 对象,如果这个对象为 null,就调用 setInitialValue() 方法

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

在 setInitialValue() 方法中,将 initialValue() 的值赋给我们想要的值,默认情况下,initialValue() 的值为 null,当然也可以重写这个方法。

    protected T initialValue() {
        return null;
    }

如果 threadLocals 对象不为 null 的话,那就取出它的 table 数组并找出 ThreadLocal 的 reference 对象在 table 数组中的位置。

从 ThreadLocal 的 set() 和 get() 方法可以看出,他们所操作的对象都是当前线程的 threalLocals 对象的 table 数组,因此在不同的线程中访问同一个 ThreadLocal 的 set() 和 get() 方法,他们对 ThreadLocal 所做的 读 / 写 操作权限仅限于各自线程的内部,这就是为什么可以在多个线程中互不干扰地存储和修改数据。

总结

ThreadLocal 是线程内部的数据存储类,每个线程中都会保存一个 ThreadLocal.ThreadLocalMap threadLocals = null;,ThreadLocalMap 是 ThreadLocal 的静态内部类,里面保存了一个 private Entry[] table 数组,这个数组就是用来保存 ThreadLocal 中的值。通过这种方式,就能让我们在多个线程中互不干扰地存储和修改数据。


参考

猜你喜欢