浅析Android中的ThreadLocal

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

一. Looper

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

    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()这个方法一起看一下:

    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方法:

/**
     * 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类。

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初始化时的代码如下:

       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中。

        /**
         * 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的值是多少:

/**
     * 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的值。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大数据架构

Java进阶(七)正确理解Thread Local的原理与适用场景

1294
来自专栏xdecode

Java高并发之无锁与Atomic源码分析

1824
来自专栏高爽的专栏

Java线程(篇外篇):线程本地变量ThreadLocal

       首先说明ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些...

1960
来自专栏小灰灰

Java并发学习之ThreadLocal使用及原理介绍

ThreadLocal使用及原理介绍 线程本地变量,每个线程保存变量的副本,对副本的改动,对其他的线程而言是透明的(即隔离的) 1. 使用姿势一览 使用方式也...

1956
来自专栏xingoo, 一个梦想做发明家的程序员

ThreadLocal从源码到应用

最早接触到ThreadLocal是在阅读dianping的Cat-client,当时对它不是很理解,就搜索了一下,大概了解是一种解决线程安全问题的机制。现在再...

1978
来自专栏陈满iOS

iOS开发·runtime原理与实践: 关联对象篇(Associated Object)(应用场景:为分类添加“属性”,为UI控件关联事件Block体,为了不重复获得某种数据)

分类(category)与关联对象(Associated Object)作为objective-c的扩展机制的两个特性:分类,可以通过它来扩展方法;Associ...

1062
来自专栏菩提树下的杨过

利用xml轻松读取web.config中的用户自定义节

虽然vs.net2.0为我们提供了 ConfigurationManager.AppSettings["..."]; ConfigurationManage...

16910
来自专栏Java编程技术

使用ThreadLocal不当可能会导致内存泄露

基础篇已经讲解了ThreadLocal的原理,本节着重来讲解下使用ThreadLocal会导致内存泄露的原因,并讲解使用ThreadLocal导致内存泄露的案例...

551
来自专栏ImportSource

设计ThreadLocal的那段日子

假设现在让你去实现一个连接类,要支持多个线程访问,同时每个线程独占一个Connection? 这时候你会怎么实现? 也许这会你想到了给每个线程传递一个Conne...

3346
来自专栏熊二哥

Java核心编程快速入门

Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示。 ? ? 反射reflect是理解Java语言...

2049

扫码关注云+社区