Android开发Android开发经验谈Android技术知识

深入理解ThreadLocal

2018-07-30  本文已影响17人  Android_Jian

前言:初识ThreadLocal这个类,还要追溯到Handler的源码分析,之后翻看任主席的《Android艺术开发探索》时再次与ThreadLocal谋面,当时对于这个类的认知很简单,就是:ThreadLocal为变量在每个线程中都创建了一个副本,每个线程都可以独立访问相应的副本变量的值,各个线程之间的访问互不影响。近期有时间翻看了下ThreadLocal的源码,来加深自己对ThreadLocal的理解,下面我们一起来学习一下。

首先我们先看一个小Demo:

public class MainActivity extends AppCompatActivity {

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

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

    public void mTest(View view){

        mBooleanLocal.set(true);
        Log.e("--------Thread#UI------",String.valueOf(mBooleanLocal.get()));

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e("---------Thread#1------",String.valueOf(mBooleanLocal.get()));
            }
        },"Thread#1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                mBooleanLocal.set(false);
                Log.e("---------Thread#2------",String.valueOf(mBooleanLocal.get()));
            }
        },"Thread#2").start();
    }
}

可以看到在MainActivity中我们只放置了一个Button按钮,mTest()方法为按钮的点击事件。点击按钮我们看下打印的日志:

07-30 13:56:32.536 15557-15557/com.example.halobear.threadlocaltest E/--------Thread#UI------: true
07-30 13:56:32.538 15557-16994/com.example.halobear.threadlocaltest E/---------Thread#1------: null
07-30 13:56:32.539 15557-16995/com.example.halobear.threadlocaltest E/---------Thread#2------: false

通过打印的日志我们可以看到,UI线程中打印的结果为true,Thread#1线程中打印的结果为null,Thread#2线程中打印的结果为false。为什么会这样子呢???让我们抱着疑惑点开ThreadLocal的源码。

我们首先看下ThreadLocal的构造方法:

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

are you kidding me ? ? ?,构造方法中竟然是一个空实现。不要着急,我们顺着源头慢慢找,接着我们看下ThreadLocal的set方法。

   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我们先简单说下:第一步,首先获取到当前线程,然后调用getMap方法,将当前线程对象传入,获取到当前线程对应的ThreadLocalMap,最后判断map是否为null,当map不为null时,调用map的set方法来存储数据(注意这里的key为this,即当前ThreadLocal对象),当map为null时,调用creatMap方法来创建map并进行数据存储。
我们点进去getMap方法看下:

    /**
     * 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;
    }

可以看到方法中直接返回了 t.threadLocals,t就是我们刚才传入的当前线程对象,threadLocals是什么东西,我们去Thread类中看一下:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

可以看到threadLocals就是我们Thread类的一个成员变量。ThreadLocal对于数据的存储,实质上是将数据存储在每个线程对象的成员变量threadLocals中。我们通过ThreadLocal来获取数据时,首先会获取到当前线程,进而获取到当前线程对象threadLocals成员变量,进而进行数据获取操作。
我们首次调用ThreadLocal的set方法时,通过getMap获取到的ThreadLocalMap肯定为null,那么程序就会走到else语句,进行ThreadLocalMap的创建以及初始化等操作。我们点击createMap方法来看一下:

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

createMap方法中果然对ThreadLocalMap对象进行了创建,我们点进去看下ThreadLocalMap的构造方法(注:ThreadLocalMap为ThreadLocal类的静态内部类):

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

可以看出:ThreadLocalMap以我们的ThreadLocal对象为key,以我们要存储的数据为value,内部是通过动态数组来实现的。INITIAL_CAPACITY为该数组的初始大小,setThreshold方法设置了扩容的临界点。具体源码我在这里就不贴出来了,有兴趣的话大家自行翻看。

上面我们分析了首次调用ThreadLocal的set方法时的操作,下面我们分析下后续调用set方法时的情形,这个时候map肯定不为null,方法会走到ThreadLocalMap的set方法,我们点进去看下:

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        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;

            // 1. 获取下标
            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) {
                    //2. 如果 k == key,对value值进行覆写
                    e.value = value;
                    return;
                }

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

            // 3. 在table数组下标 i 处创建新节点,进行赋值操作(一个线程中对应多个ThreadLocal对象时)
            tab[i] = new Entry(key, value);
            int sz = ++size;

            if (!cleanSomeSlots(i, sz) && sz >= threshold)

                //4. 当前元素个数大于等于threshold临界点时,进行扩容操作
                rehash();
        }

关键点地方在上述代码中已经标注了。

ThreadLocal的set方法我们大致分析完毕了,接下来我们看下ThreadLocal的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) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

可以看出在ThreadLocal的get方法中,首先获取到当前线程,进而获取到当前线程对象threadLocals成员变量,接下来对map进行判断,如果map不为空,则调用map的getEntry方法,获取到Entry元素,进而获取到value值。如果map为null,则调用setInitialValue()方法。我们对map为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()方法,我们点进去看下:

    protected T initialValue() {
        return null;
    }

可以看出initialValue()方法直接return null。其实这个方法是用来初始化value操作的。当我们没有调用ThreadLocal的set方法,直接调用get方法的时候,这种情况下map肯定为null,最后程序会调用到initialValue方法,将initialValue方法中的返回值直接return出去。这也就是在我们的示例Demo中,Thread#1线程打印结果为null的原因。我们创建ThreadLocal对象的时候可以重写该方法,用来对value值进行初始化操作。

在上述ThreadLocal的get方法中,不知道你有没有注意到这样一个细节,为什么在get方法中已经对map进行了null判断,后续在setInitialValue()方法中再一次对map进行null判断???我的理解是,这样子做是考虑到一个线程中创建多个ThreadLocal对象这种情况。当一个线程中只有一个ThreadLocal对象的时候,如果我们没有调用ThreadLocal的set方法,直接调用到ThreadLocal对象的get方法,这个时候程序在get方法中对map进行判断肯定为null,程序走到setInitialValue方法中,接下来再次对map进行判断,map肯定为null,很显然走else语句块中的createMap方法。

接下来我们考虑下一个线程中创建多个ThreadLocal对象的情况。假设我们在一个线程中创建了两个ThreadLocal对象,分别为localOne和localTwo,如下所示:

        new Thread(new Runnable() {
            @Override
            public void run() {

                ThreadLocal<Integer> localOne = new ThreadLocal<>();
                ThreadLocal<String> localTwo = new ThreadLocal<>();

                localOne.set(2);
                Log.e("---------Thread------",String.valueOf(localOne.get()));

                Log.e("---------Thread------",String.valueOf(localTwo.get()));
            }
        }).start();

我们首先创建了localOne对象,并调用了它的set方法和get方法,接着我们创建了localTwo对象,直接调用它的get方法。注意:虽然localOne和localTwo为两个不同的对象,但都在同一个线程中创建,它们所依附的threadLocals成员变量是同一个,也可以说map为同一个。我们对localTwo“直接调用get方法”进行分析,在get方法中首先对map进行判断,因为localTwo的执行顺序在localOne之后,此时map肯定不为null,获取到的Entry元素e为null,程序还是走到setInitialValue()方法中,再次对map进行判断,map不为null,这个时候就会走到map的set方法。

关于ThreadLocal的分析就到这里了,我们下期再会哈哈。

上一篇下一篇

猜你喜欢

热点阅读