Android之ThreadLocal

一、概述

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

在日常开发中用到ThreadLocal的地方较少,但在某些特殊的场景下,通过ThreadLocal可以轻松地实现一些看起来很复杂的功能。下面例举两个场景:

  • 一般来说,当某些数据是以线程为作用域且不同线程具有不同的数据副本时,可以考虑使用ThreadLocal。比如对于Handler而言,它需要获取当前线程的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。
  • 复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码的多样性等,这是就可以采用ThreadLocal来让监听器作为线程内的全局对象。

二、示例

上述关于ThreadLocal的概念可能有点抽象,下面将通过一个一个例子来演示ThreadLocal的真正含义:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    //定义ThreadLocal对象
    private ThreadLocal mBooleanThreadLocal = new ThreadLocal<>();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBooleanThreadLocal.set(true);
        Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());

        new Thread("Thread#1") {
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
            }
        }.start();
    }
}

在上面的代码中,在主线程中设置mBooleanThreadLocal的值为true,在子线程1中设置mBooleanThreadLocal值为false,子线程2中不设置mBooleanThreadLocal的值。然后在3个线程中分别通过get方法获取值并打印出来。根据前面的描述,期望的打印结果应该是:主线程为true,子线程1为false,子线程2位null,因为子线程2中没有设置值。实际打印结果如下:

与预期结果一致。虽然在不同线程中访问的是同一个mBooleanThreadLocal对象,但是她们通过ThreadLocal获取到的值是不一样的,这就是ThreadLocal的奇妙之处。结合这个例子然后再看一遍前面对ThreadLocal的两个使用场景的理论分析,应该能够较好的理解ThreadLocal的使用方法了。

三、工作原理

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

首先看ThreadLocalset方法,如下所示:

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方法来获取当前线程中的ThreadLocal数据,如果返回的ThreadLocalMap对象不为空,则调用set方法添加数据,否则就创建一个新对象并把值添加进去。ThreadLocalMap中定义了一个private Entry[] table;来存储数据,我们可以通过set方法将数据添加到table数组中,具体方法如下所示:

private void set(ThreadLocal key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

上面的代码实现了数据的存储过程,这里不去分析它的具体算法,但是我们可以看到ThreadLocal的值的存储索引计算方法为i = key.threadLocalHashCode & (len-1);

上面分析了ThreadLocalset方法,下面来看get方法:

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

可以发现,ThreadLocalget方法逻辑也比较清晰,同样是先取出当前线程的ThreadLocalMap 对象,如果这个对象为null就返回初始值,这个初始值由ThreadLocalinitialValue()方法来描述,默认情况下为null,当然也可以重写这个方法,它的默认实现如下所示:

protected T initialValue() {
    return null;
}

如果ThreadLocalMap 对象不为null,那么就取出table数组并找到相应的值返回回去。

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

:上述源码版本为API26,不同版本源码可能会有些许差异。

你可能感兴趣的:(Android之ThreadLocal)