Android 的消息机制之 ThreadLocal

ThreadLocal的工作原理(作用域即线程)
        ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只能在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。通过ThreadLocal 可以轻松的获取到每个线程的Looper,线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper
        一般来说,当某些数据是以线程为作用域并且不同的线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal,如Android 源码中的Looper、ActivityThread、以及AMS中都用到了ThreadLocal,比如Handler,他需要获取当先线程的Looper,而Looper 的作用域就是线程,且不同的线程具有不同的Looper,此时通过ThreadLocal 就可以轻松实现Looper 在线程中的存取。

        ThreadLocal 的另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,如函数调用栈比较深以及代码入口的多样性,在这种情况下,我们有需要监听器能够贯穿整个线程的执行过程,这个时候就可以采用ThreadLocal,可以让监听器作为线程内的全局对象而存在,在线程内部只需要通过get 方法就可以获取到监听器。
如果不采用ThreadLocal,那么有以下两种方法可以使用:
1、将监听器通过参数的形式在函数的调用栈中进行传递
    缺点:在函数调用栈很深的时候,通过函数参数来传递监听器对象几乎不可接受,这会让程序的设计看起来很糟糕就是很复乱,一个对象传递了N 次,不利于维护
2、将监听器作为静态变量供线程访问
    缺点:这种状态不具有可扩展性,比如同时有两个线程在执行,就需要提供2个静态的监听器对象,如果有10个呢?而采用ThreadLocal每个监听器对象都在自己的线程内部存储,根本不才在方法2的这种问题

栗子:

public class WelcomeActivity
        extends BaseActivity<ActivityWelcomeBinding>
{

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


    @Override
    protected int getLayoutResId() {
        return R.layout.activity_welcome;
    }

    @Override
    public void init() {
        Logger.t("WelcomeActivity");
        mBooleanThreadLocal.set(true);
        Logger.d("[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get());
    
        new Thread("Thread#1"){
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Logger.d("[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.get());
            }
        }.start();
        new Thread("Thread#2"){
            @Override
            public void run() {
                Logger.d("[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get());
            }
        }.start();

    }
}

        在代码中,在主线程中设置mBooleanThreadLocal 的值为 true,在子线程1中设置mBooleanThreadLocal 的值为 false,在子线程2中不设置mBooleanThreadLocal 的值,根据前面的描述,此时主线程中应该是 true,子线程1中应该是 false,子线程2中由于没有设置值,所以应该是 null。运行程序日志如下:

    从日志中可以看书,虽然在不同线程中访问的是同一个ThreadLocal 对象,但通过ThreadLocal 获取到的值却不一样 ,这是因为不同线程访问同一个ThreadLocal 的get 方法时,ThreadLocal 内部会从各自的线程中取出一个数组,然后在从数组中根据当前ThreadLocal 的索引去查找出对应的 value值,而不同线程中的数组是不同的,这就是为什么通过ThreadLocal 可以在不同的线程中维护一套数据的副本并且彼此互不干扰的原因。

ThreadLocal 的内部实现:
    ThreadLocal 是一个泛型类,它的定义为 public class ThreadLocal<T>,只要弄清楚ThreadLocal 的get 和 set 方法就可以明白它的工作原理
1、ThreadLocal 的set 方法:

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null){
            values=initializeValues(currentThread);
        }
        values.put(this,value);
    }

    在上面的 set 方法中,首先会通过 values 方法获取到当前线程中的ThreadLocal 数据,在Thread 类的内部有一个成员专门用于存储线程的ThreadLocal 的数据(ThreadLocal.Values localValues);如下所示:

    /**
     * Gets Values instance for this thread and variable type.
     */
    Values values(Thread current) {
        return current.localValues;
    }

    如果localValues 的值为 null,则需对其进行初始化,初始化后在将ThreadLocal 的值进行存储,在localValues 中内部有一个数组:private Object[] table,ThreadLocal 的值就存在这个 table 数组中

    /**
     * Creates Values instance for this thread and variable type.
     */
    Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }

    localValues 的put 方法:

        /**
         * Sets entry for given ThreadLocal to given value, creating an
         * entry if necessary.
         */
        void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

    从以上代码可以得出ThreadLocal 的值在 table 数组中的存储位置总是为 ThreadLocal 的 reference 字段所标识的对象的下一个位置,比如ThreadLocal 的 reference 对象在table 数组中的索引为 index,则ThreadLocal 的值在 table 数组中的索引就是 index+1。最终ThreadLocal 的值将会被存储在 table 数组中:table[index + 1] = value;

    ThreadLocal 的get 方法:

    @SuppressWarnings("unchecked")
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

    首先会通过 values 方法获取到当前线程中的ThreadLocal 数据对象,如果这个对象为 null 则返回初始值,getAfterMiss 源码如下:

        /**
         * Gets value for given ThreadLocal after not finding it in the first
         * slot.
         */
        Object getAfterMiss(ThreadLocal<?> key) {
            Object[] table = this.table;
            int index = key.hash & mask;

            // If the first slot is empty, the search is over.
            if (table[index] == null) {
                Object value = key.initialValue();

                // If the table is still the same and the slot is still empty...
                if (this.table == table && table[index] == null) {
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;

                    cleanUp();
                    return value;
                }

                // The table changed during initialValue().
                put(key, value);
                return value;
            }

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            // Continue search.
            for (index = next(index);; index = next(index)) {
                Object reference = table[index];
                if (reference == key.reference) {
                    return table[index + 1];
                }

                // If no entry was found...
                if (reference == null) {
                    Object value = key.initialValue();

                    // If the table is still the same...
                    if (this.table == table) {
                        // If we passed a tombstone and that slot still
                        // contains a tombstone...
                        if (firstTombstone > -1
                                && table[firstTombstone] == TOMBSTONE) {
                            table[firstTombstone] = key.reference;
                            table[firstTombstone + 1] = value;
                            tombstones--;
                            size++;

                            // No need to clean up here. We aren't filling
                            // in a null slot.
                            return value;
                        }

                        // If this slot is still empty...
                        if (table[index] == null) {
                            table[index] = key.reference;
                            table[index + 1] = value;
                            size++;

                            cleanUp();
                            return value;
                        }
                    }

                    // The table changed during initialValue().
                    put(key, value);
                    return value;
                }

                if (firstTombstone == -1 && reference == TOMBSTONE) {
                    // Keep track of this tombstone so we can overwrite it.
                    firstTombstone = index;
                }
            }
        }

   初始值由ThreadLocal 的 initialValue 方法来描述,默认情况下为null,也可重写这个方法,其默认实现如下:

protected T initialValue(){
    return null;
}

    如果 localValues 对象不为null,则取出它的 table 数组并找出 ThreadLocal 的reference  对象在table 数组中的位置,然后 table 数组中的下一个位置所存储的数据就是 ThreadLocal 的值

    由ThreadLocal 的set 和 get 方法可以看出,他们所操作的对象都是当前线程的 localValues 对象的 table 数组,故在不同线程中访问同一个 ThreadLocal 的set 和 get 方法,他们对ThreadLocal 所做的读/写操作仅限与各自线程的内部,即ThreadLocal可以再多个线程中互不干扰地存储和修改数据的原因


总结:
    ThreadLocal 通过set 方法存储该线程的数据,在set 方法中,会首先获取该线程的 ThreadLocal 数据,如果不存在,则会新建一个ThreadLocal,然后以该线程作为地址索引,将需要存储的值放在线程索引的下一位,这样不同线程中的值由于在Value 数组中存储的地址不同,故不会相互干扰。通过 get 方法获取数据,如果该线程不存在 ThreadLocal 数据,则直接返回,如果存在,则通过获取该 ThreadLocal 在数组中的索引,而索引的下一位就是属于该线程的数据。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值