美高梅网址注册-澳门mgm4858集团登录网址
做最好的网站
来自 澳门mgm4858集团登录网址 2019-09-30 15:41 的文章
当前位置: 美高梅网址注册 > 澳门mgm4858集团登录网址 > 正文

ThreadLocal 是一个线程内部的数据存储类,只有在

前言

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

ThreadLocal

在Android开发中,Handler消息处理机制中用到了ThreadLocal类,花了点时间对它进行解析。

Thread类中有个成员变量threadLocals,类型为ThreadLocal.ThreadLocalMap,是ThreadLocal自定义的一个hashmap,它的key是ThreadLocal<?>类型,value是Object。如下:

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

注意:在Thread中threadLocals参数并没有被赋值,所以默认为null

ThreadLocal类中,有这样一个函数

    //初始化值
    protected T initialValue() {
        return null;
    }

这个函数用来初始化在一个线程中的初始值,默认返回null,建议使用ThreadLocal时重写。

再看看这个函数

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

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

从setInitialValue()方法名可以看出是设置ThreadLocal的初始值,先是获取当前线程 t ,然后通过getMap(t)方法获取当前线程 t 的threadLocals变量,就是一个ThreadLocalMap实例,通过上面的getMap(Thread t)方法看出返回的map应该是null,所以执行createMap(t,value)方法。

    /**
     * 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()方法使Thread的成员变量threadLocals赋值了,并把this当做key,把通过initialValue()方法返回的值作为value(所以重写initialValue()的话就避免了初始化值为null的尴尬)。

同时我们发现在ThreadLocal类中的set()方法也调用了createMap()方法,是不是有种顿时豁然开朗的感觉。

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

我们先看看哪里调用了setInitialValue()法

    /**
     * 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类中只有这一个地方调用了这个方法。看了这里就应该明白了吧。

  • 在没有调用set()方法的情况下, 如果第一次调用get()方法,getMap()肯定返回一个null的ThreadLocalMap对象,就会执行 return setInitialValue(); 语句,返回初始化的值。
  • 如果调用了set()方法的情况下,可以看出set方法也调用了createMap(t, value);来给Thread的成员变量threadLocals赋值,那么getMap()肯定就返回一个不为null的ThreadLocalMap对象,把this作为对象来获取value。

小结

  • Thead中有一个类型为ThreadLocalMap的成员变量threadLocals,并且初始值为null
  • ThreadLocalMap是一个ThreadLocal自定义的HashMap,键为ThreadLocal<?>,值为Object
  • ThreadLocal默认会有一个初始值null,你可以通过重写initialValue()方法或者调用set()方法来改变这个初始值,他们的本质都是去调用createMap()方法给当前的线程Thread的成员变量threadLocals赋值。
  • ThreadLocal的set()方法通过获取当前线程 t ,通过 t 的成员变量threadLocals的set()方法来去保存value值,并把当前对象this作为key。
  • 调用ThreadLocal的get()方法来获取value,实质就是获取当前线程t的成员变量threadLocals,并且把自身作为key来获取value的过程。

每个Thread都维护了一个ThreadLocalMap对象,也就是threadLocals变量,通过它就可以保存各种不同类型的ThreadLocal和对应的值

Demo示例:

public class ThreadStudy {

    private static OneThread oneThread;
    private static TwoThread twoThread;
    private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 99;
        }
    };
    private static ThreadLocal<String> stringThreadLocal = new InheritableThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "Hello world";
        }
    };

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        oneThread = new ThreadStudy().new OneThread();
        twoThread = new ThreadStudy().new TwoThread();
        oneThread.start();
        twoThread.start();

    }

    class OneThread extends Thread{

        public OneThread(){
        }

        @Override
        public void run() {
            //给Thread的threadLocals变量赋值,并把stringThreadLocal作为key,"设置value值"作为value保存在threadLocals里。
            stringThreadLocal.set("设置value值");
            System.out.println(Thread.currentThread().getName() + "  " + stringThreadLocal.get());
        }
    }

    class TwoThread extends Thread{

        public TwoThread(){
        }
        @Override
        public void run() {
            //由于没有调用set方法,所以会在第一次调用get()方法中去个给Thread的threadLocals变量赋值
            System.out.println(Thread.currentThread().getName() + "  " + integerThreadLocal.get());
            System.out.println(Thread.currentThread().getName() + "  " + stringThreadLocal.get());
        }
    }
}

输出结果:

Thread-0 设置value值
Thread-1 99
Thread-1 Hello world

相信看到这里对ThreadLocal都理解了吧。

话说ThreadLocalMap类到底怎么工作的呢,下面我们一起来看看。

原文发表于:http://blog.csdn.net/qq_27485935 , 大家没事可以去逛逛 (ง •̀_•́)ง

一、ThreadLocal 是什么

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

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

ThreadLocalMap

在ThreadLocalMap中有个静态内部类Entry

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

Entry继承了WeakReference(弱引用)类,应该这里准确的说是ThreadLocal作为Entry的key并成了弱引用。

其实在HashMap中也有这样的一个同名的接口类,关系是:HashMap<K,V> 继承了AbstractMap<K,V> ,然后AbstractMap<K,V> 实现了 Map<K,V>,在Map<K,V>接口中就有了Entry<K,V>接口。

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

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

ThreadLocalMap和HashMap一样默认容量16,并且用数组数组实现。在ThreadLoca中通过set方法类添加数据,从源码中可以看出实际是调用了ThreadLocalMap类的set方法,我们就先来看看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;
            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) {
                    e.value = value;
                    return;
                }

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

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

int i = key.threadLocalHashCode & (len-1);

通过ThreadLocal的threadLocalHashCode 参数,可以理解为HashCode(int类型hash值,每个ThreadLocal对应一个),和table数组的长度-1的差做“与”运算得到元素在数组中的下标。然后从table数组中取出对应下标的Entry判断是否为null,如果没有发生冲突(取出的Entry == null)则给对应的下标赋值。如果发生了冲突(取出的Entry != null),则比较冲突的Entry的key是否和当前的set(ThreadLoacal key,Object value)的参数key相同,如果是相同的则覆盖原来的value并结束。如果参数key和获取的Entry的key不相等,这个时候就需要解决冲突,这里是通过向后移动下标,即下标 +1(这里和HashMap解决冲突不同)来解决的。

解决冲突如下:

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

那么ThreadLocalMap添加数据的过程完成了。ThreadLocal中通过get方法取出保存的数据,从源码中也可以看出实际是调用了ThreadLocalMap的getEntry方法。

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

getEntry先是通过int i = key.threadLocalHashCode & (table.length - 1);取得对应的下标,和前面的set方法的获取下标方式相对应。如果取到的Entry值不为null而且key也相同就返回取到的Entry。由于在添加Entry的时候有可能发生冲突,那么在取得时候就可能不能一次性通过下标取到对应的值,如果发生这样的情况就调用
getEntryAfterMiss()来获取。

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

这里必须和前面解决冲突的思路一致,如果没有一次性取到对应的Entry,下标就向后移动(+1),然后在取出新的下标的值进行比较,如果符合条件就返回。如果取出的Entry为null,则返回null。

默认的ThreadLocalMap容量只有16,如果存放的数据多了,那么就跟HashMap一样需要扩容,默认情况下当存储的数据量超过容量的2/3的时候就会扩容为之前容量的2倍。

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

       /**
         * Double the capacity of the table.
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

相信你看到这里对ThreadLocal有更深入的理解了吧。

ThreadLocal

概述: 位于 java.lang 包下, 是一个线程内部的数据存储类, 通过它可以在指定线程中存储数据, 数据存储之后, 只有在指定线程中才可以获取到存储的数据。

使用场景: Looper、 ActivityThread

基本用法:

mThreadLocal = new ThreadLocal<Integer>();
mThreadLocal.set(10);  // 通过 set 方法设值
mThreadLocal.get();  // 通过 get 方法取值

二、基本用法

ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();

mStringThreadLocal.set("developerHaoz");

mStringThreadLocal.get();

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

 private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBooleanThreadLocal.set; Log.d(TAG, "Current Thread: mBooleanThrealLocal is : " + mBooleanThreadLocal.get; new Thread("Thread#1"){ @Override public void run() { mBooleanThreadLocal.set; Log.d(TAG, "Thread 1: mBooleanThrealLocal is : " + mBooleanThreadLocal.get; } }.start(); new Thread("Thread#2"){ @Override public void run() { Log.d(TAG, "Thread 2: mBooleanThrealLocal is : " + mBooleanThreadLocal.get; } }.start(); }

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

图片 1image.png

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

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

源码解析

本文由美高梅网址注册发布于澳门mgm4858集团登录网址,转载请注明出处:ThreadLocal 是一个线程内部的数据存储类,只有在

关键词: