专栏首页Android开发实战多线程之 ThreadLocal简析

多线程之 ThreadLocal简析

点击上方蓝字,记得关注我们!

前言

源码 基于 Android SDK 28 JDK 1.8


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

一、ThreadLocal 是什么


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

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

二、基本用法


创建,支持泛型

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

set 方法

mBooleanThreadLocal.set(false);

get 方法

mBooleanThreadLocal.get()

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

public class MainActivity extends AppCompatActivity {

    String TAG = "ThreadLocal";
    ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBooleanThreadLocal.set(true);
        Log.d(TAG, "Current Thread: mBooleanThreadLocal is : " + mBooleanThreadLocal.get());
        new Thread("Thread#1") {
            @Override
            public void run() {
                super.run();
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "Thread 1: mBooleanThreadLocal is : " + mBooleanThreadLocal.get());
            }
        }.start();
        new Thread("Thread#2") {
            @Override
            public void run() {
                super.run();
                Log.d(TAG, "Thread 2: mBooleanThreadLocal is : " + mBooleanThreadLocal.get());
            }
        }.start();

    }
}

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

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

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

三、ThrealLocal 工作原理


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

1、ThrealLocal 的 set() 方法

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
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 然后再进行设置。

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
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 数组中。

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

2、ThrealLocal 的 get() 方法

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

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
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() 方法

/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
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,当然也可以重写这个方法。

   /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the <tt>initialValue</tt> method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns <tt>null</tt>; if the
     * programmer desires thread-local variables to have an initial
     * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */

    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 中的值。通过这种方式,就能让我们在多个线程中互不干扰地存储和修改数据

本文分享自微信公众号 - 编码恒字诀(gh_db8538619cdd),作者:Anymarvel

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Centos安装最新版本cmake

    这里建议下载的源码进行编译(下载二进制文件,虽然可以执行,但你并不知道会有什么动态链接库的报错,出现了问题也很难解决)

    Anymarvel
  • WorkManager _Android新架构组件

    5月8号, I/O大会上推出了Architeture新组件WorkManager。 由于Android版本的不断更新,后台任务的处理变得越来越复杂。 因此,Go...

    Anymarvel
  • 警告:Android P(禁用非官方API)

    Android P(禁用非官方API)目前类似的消息此起彼伏,关于国外移动开发者社区 XDA 的活跃者们在 AOSP(Android 开放源代码...

    Anymarvel
  • ThreadLocal<T> 源码解析

    在activeJDBC框架内部的实现中看到了 ThreadLocal 这个类,记录下了每个线程独有的连接

    Theone67
  • 谈谈那个听起来挺高大上的ThreadLocal

    用户1269200
  • 高并发编程-ThreadLocal深入解析

    ThreadLocal的实现原理是每一个Thread维护一个ThreadLocalMap映射表,映射表的key是ThreadLocal实例,并且使用的是Thre...

    JavaQ
  • 多目标的强化学习教程-两篇均有代码

    用户1908973
  • 多目标的强化学习教程-两篇均有代码

    用户1908973
  • CNN层和特征可视化VGG-16

    CNN由处理视觉信息的层组成。CNN首先接收输入图像,然后将其传递通过这些层。有几种不同类型的层:最常用的层:卷积,池化和完全连接的层。 首先,让我们来看看完...

    小飞侠xp
  • Angular应用的路由指令RouterLink

    Angular应用的路由指令RouterLinkapp Component html源代码:

    Jerry Wang

扫码关注云+社区

领取腾讯云代金券