前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅析Android中的ThreadLocal

浅析Android中的ThreadLocal

作者头像
MelonTeam
发布2018-01-04 15:39:26
5500
发布2018-01-04 15:39:26
举报
文章被收录于专栏:MelonTeam专栏MelonTeam专栏

ThreadLocal第一眼很容易让人误以为这是一个Thread,其实并不是,它是在JDK 1.2中引入,为每个线程提供一个独立的本地变量副本,用来解决变量并发访问的冲突问题。所有的线程可以共享一个ThreadLocal对象,但是每一个线程只能访问自己所存储的变量,线程之间互不影响。那为什么标题中说的是Android中的ThreadLocal呢,原因是Android中的ThreadLocal和JDK的ThreadLocal代码实现上是有一定区别的,虽然最终实现的效果是一样的。

一. Looper

ThreadLocal在业务开发中使用的场景比较少,所以对大家来说会比较陌生,但是它却和我们平时写的每一行代码都息息相关,接下来一起看一段简单的代码:

代码语言:javascript
复制
    class MyThread extends HandlerThread {
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            Looper.prepare();
            handler = new Handler();
            Looper.loop();
        }
    }

我们知道,一个线程本身是不存在消息循环的,我们需要手动调用Looper.prepare(),然后再调用Looper.loop()创建一个消息循环,这样的话这个线程就有了自己的一个消息循环Looper,我们可以通过Looper.myLooper()获取当前所在线程的Looper, 那么这个Looper是保存在哪里的呢?我们点进Looper.prepare()这个方法一起看一下:

代码语言:javascript
复制
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

首先创建了一个Looper对象,在Looper对象的构造函数中创建了一个消息队列,然后把这个Looper对象放在一个静态的成员变量sThreadLocal里头。因此所有支持消息循环的线程都共享一个ThreadLocal对象,但是同时也可以获取到只属于自己线程的Looper,线程之间互不影响。当我们调用handler的post的方法时候,其实就是把消息丢进了创建该handler时所在的线程的消息队列里头,终止被该线程的Looper所执行。

二.ThreadLocal源码分析

看到这里可能大家就会想,Looper在sThreadLocal内部是如何存储呢?我们一起进去看下set方法:

代码语言:javascript
复制
/**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

 先获取当前的线程 currentThread, 然后取出currentThread的一个成员变量values,这个values其实就在最终存value的数据结构,如果values为空的话通过initializeValues为其创建一个values。我们一起看下这个Values类。

代码语言:javascript
复制
static class Values {

        /**
         * Size must always be a power of 2.
         */
        private static final int INITIAL_SIZE = 16;

        /**
         * Placeholder for deleted entries.
         */
        private static final Object TOMBSTONE = new Object();

        /**
         * Map entries. Contains alternating keys (ThreadLocal) and values.
         * The length is always a power of 2.
         */
        private Object[] table;

        /** Used to turn hashes into indices. */
        private int mask;

        /** Number of live entries. */
        private int size;

        /** Number of tombstones. */
        private int tombstones;

        /** Maximum number of live entries and tombstones. */
        private int maximumLoad;

        /** Points to the next cell to clean up. */
        private int clean;

Values初始化时的代码如下:

代码语言:javascript
复制
       Values() {
            initializeTable(INITIAL_SIZE);
            this.size = 0;
            this.tombstones = 0;
        }

       private void initializeTable(int capacity) {
            this.table = new Object[capacity * 2];
            this.mask = table.length - 1;
            this.clean = 0;
            this.maximumLoad = capacity * 2 / 3; // 2/3
        }

这个table是用于存储我们用到的数组,但是它不仅仅存储value的值,他会先存储当前value所对应的值ThreadLocal的弱引用,然后在下一位存储value的值。可知table的长度一定是2的N次方,而mask的值则为2的N次方减一,maximumLoad这个值则是用来判断是否需要扩充table的数组大小。

初始化完Values,我们就需要把value  put进 values中。

代码语言:javascript
复制
        /**
         * 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;
                }
            }
        }

在开头先调用了cleanUp方法,顾名思义就是释放掉当前table数字中已经失效的value值。然后通过一个for循环寻找当前的value值需要存放的位置,这里起始的index使用的是key.hash & mask,这样做的用意是什么呢? 我们知道mask的值为2的N次方减一,所以它的二进制的值每一位都是1,我们再看下key.hash的值是多少:

代码语言:javascript
复制
/**
     * Internal hash. We deliberately don't bother with #hashCode().
     * Hashes must be even. This ensures that the result of
     * (hash & (table.length - 1)) points to a key and not a value.
     *
     * We increment by Doug Lea's Magic Number(TM) (*2 since keys are in
     * every other bucket) to help prevent clustering.
     */
    private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);

 这是一个神奇的数字0x61c88647,具体为什么取这个值我们暂时不深究,为的是让这个table上值存储不重复,而且分散。最后key.hash & mask的值会落在table中的某个位置。接下来在for循环中如果我们在table中找到了ThreadLocal的弱引用,则替换它的下一位的value的值。如果没有找到对应的ThreadLocal的引用,则在table中存入当前value所对应的ThreadLocal的弱引用,并在下一位存入value的值。然后我们通过ThreadLocal的get方法查找时,其实也是先查找到对应的ThreadLocal的弱引用,然后下一位才是对应的value的值。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-06-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. Looper
  • 二.ThreadLocal源码分析
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档