美文网首页
ThreadLocal

ThreadLocal

作者: Time_x | 来源:发表于2019-05-15 23:36 被阅读0次

    研究过Handler的应该对ThreadLocal比较眼熟的,线程中的Handler对象就是通过ThreadLocal来存放的。初识ThreadLocal的可能被它的名字有所误导,ThreadLocal初一看可能会觉得这是某种线程实现,而实际并非如此。事实上,它是一个全局变量,用来存储对应Thread的本地变量,这也是为什么将其称之为Local。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

     比如Handler,当我们在一个线程中创建了一个Handler时,在调用Looper.prepare()时通过ThreadLocal保存了当前线程下的Looper对象,而所有线程的Looper都由一个ThreadLocal来维护,也就是在所有线程中创建的Looper都存放在了一个ThreadLocal中,然后创建Handler将Handler与当前线程Looper关联,当调用Looper.loop()的时候通过myLooper()得到的就是当前线程的Looper,当在其他线程使用Handler来发送消息的时候,其实也就是将对应的Message存储到了对应Handler的MessageQueue中,当Looper去分发消息的时候,就是将当前线程中的Looper对应的MessageQueue中的Message通过Handler的回调返回给了Handler所在的线程。如对Handler不了解的,可参考Handler全面解读

    ThreadLocal模拟

     那么,如何来做到区分不同线程中的变量呢?我们这里模拟一个实现ThreadLocal功能的类,原理大致一样。

    public class ThrealLocalImitation<T> {

        private static Map<Thread, Object> sSaveValues = new HashMap<Thread, Object>();

        public synchronized void set(T threadData) {

            Thread thread = Thread.currentThread();

            mSaveValues.put(thread, threadData);

        }

        public synchronized T get() {

            Thread thread = Thread.currentThread();

            return (T) mSaveValues.get(thread);

        }

    }

     如上ThreadLocal模仿类里面,通过全局的Map变量sSaveValues,以Thread为key,value为对应线程需要保存的变量,实现了,每个线程对应保存了一个变量,在不同的线程存储不同的变量,通过get方法就能取回对应的值。

    ThreadLocal原理分析

     ThreadLocal类通过set、get方法来分别存取变量的,搞懂了这两个方法的功能也就明白ThreadLocal的原理了,所以重点分析这两个方法。

    在进入正式的分析之前先来看一个类——ThreadLocalMap

     和我们模拟的ThreadLocal稍有所区别,ThreadLocal不是直接通过一个Map来存储Thread和value对应关系的。

     在Thread类中,有一个变量ThreadLocal.ThreadLocalMap。

     先看下该类的其中一个构造方法

        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);

            }

        static class Entry extends WeakReference<ThreadLocal<?>> {

                /** The value associated with this ThreadLocal. */

                Object value;

                Entry(ThreadLocal<?> k, Object v) {

                    super(k);

                    value = v;

                }

            }

     ThreadLocalMap内部维护一个数组Entry[] table, Entry对应存储了key–value(ThreadLocal—value)。ThreadLocalMap实际上是一个实现了自定义的寻址方式的HashMap。

     那么ThreadLocal是如何存储线程本地变量的呢?先给个简单的结论。

    每个Thread在生命周期中都会维护着一个ThreadLocalMap,可以看成是一个存储了ThreadLocal(key)—value的HashMap,当ThreadLocal存储value时,先通过当前Thread得到其维护的ThreadLocalMap,然后将其存储到该map中,而获取value时则是先获取到当前线程的ThreadLocalMap,然后通过当前的ThreadLocal,获取到ThreadLocalMap存储的value值。

    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);

    }

     set方法中,首先通过Thread.currentThread()获取到当前的线程,通过当前线程获得其维护的ThreadLocalMap,当map为空时,则为当前Thread创建一个ThreadLocalMap,不为空的话则将ThreadLocal–value存储到map中。

    所以一个Thread对应着一个ThreadLocalMap,而一个ThreadLocalMap对应着多个ThreadLocal。

    get()方法

        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();

        }

    1

     get()方法,同样是先获取到当前Thread,然后获取到当前Thread的ThreadLocalMap,然后根据ThreadLocal自身,通过ThreadLocalMap自身的寻址方式获取到存储ThreadLocal和value的Entry对象,进而得到value。

     setInitialValue();是当ThreadLocalMap为空时,可以通过实现ThreadLocal的initialValue()来获得一个默认值,同时该默认值会被存储到线程的ThreadLocalMap中。

    内存泄漏

     分析到这里,ThreadLocal的原理已经很明朗了。但是一些使用不当的情况出现内存泄漏的风险,所以最后讲解下ThreadLocal会出现的内存泄漏风险,及如何避免。

     ThreadLocalMap中存储的Entry为ThreadLocal–value,准确的描述应该是weakReference(ThreadLocal)–value,即,key(ThreadLocal为弱引用),而value则是强引用的,当ThreadLocal为空后,Thread不会再持有ThreadLocal引用,ThreadLocal可以被GC回收,但是Thread的ThreadLocalMap仍然还持有value的强引用,导致value需要等待线程生命周期结束才可能被GC回收。当出现一些长时间存在的线程,不断的存储了内存比较大的value,而value实际是不再被使用的,value由于线程没有被回收而不断的堆积,造成了内存泄漏。比如当使用到线程池是,Thread很有可能不会被马上结束,可能会被不断的重复利用。

     所以这里引入ThreadLocal的另外一个方法——remove方法

    remove()方法

        public void remove() {

            ThreadLocalMap m = getMap(Thread.currentThread());

            if (m != null)

                m.remove(this);

        }

        //ThreadLocalMap中的remove

        private void remove(ThreadLocal<?> key) {

                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)]) {

                    if (e.get() == key) {

                        e.clear();

                        expungeStaleEntry(i);

                        return;

                    }

                }

            }

     所以在value不再使用时,应该及时调用remove,解除线程对该value的引用

    原文:https://blog.csdn.net/wsq_tomato/article/details/82390262

    相关文章

      网友评论

          本文标题:ThreadLocal

          本文链接:https://www.haomeiwen.com/subject/fymvaqtx.html