美文网首页
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