深入分析ThreadLocal
执著梦飞
8年前
<p>这篇文章主要分析Android中的ThreadLocal原理以及相关问题, 也分析与其在Java中内部实现的区别, 让大家理解ThreadLocal的使用场景与正确使用方法.</p> <h3><strong>ThreadLocal的定义</strong></h3> <p>Android源码中描述:</p> <pre> <code class="language-java">Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same {@code ThreadLocal} object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports {@code null} values. 实现了线程局部变量的存储. 所有线程共享同一个ThreadLocal对象, 但是每个线程只能访问和修改自己存储的变量, 不会影响其他线程. 此实现支持存储null变量.</code></pre> <p>从上面的定义看出, 关键的地方即: ThreadLocal对象是多线程共享的, 但每个线程持有自己的线程局部变量. ThreadLocal不是用来解决共享对象问题的, 而是提供线程局部变量, 让线程之间不会互相干扰.</p> <p>下面看在Android中Looper的应用, 每个线程只有一个Looper对象:</p> <pre> <code class="language-java">static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 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)); } /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); } </code></pre> <p>在了解ThreadLocal的作用后, 也会产生一些疑问:</p> <p>线程局部变量是怎么存储的?</p> <p>是怎么做到线程间相互独立的?</p> <p>接下来在分析Android的ThreadLocal源码的过程中, 理解其实现原理, 并解决上面的疑问.</p> <h3><strong>ThreadLocal的实现原理</strong></h3> <p>ThreadLocal有三个主要的public方法: set, get, remove.</p> <p>ThreadLocal是通过set方法存储局部变量的, 所以先从set方法看起:</p> <pre> <code class="language-java">public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); } /** * Gets Values instance for this thread and variable type. */ Values values(Thread current) { return current.localValues; } /** * Creates Values instance for this thread and variable type. */ Values initializeValues(Thread current) { return current.localValues = new Values(); } </code></pre> <p>set方法中根据当前线程获得 Values , 线程局部变量也是存储在 Values 中, 而不是ThreadLocal对象中. 如果一开始values为null, 就通过initializeValues方法初始化. 上面代码根据线程获得的 values 变量就是Thread对象的localValues变量, 可看下Thread源码中相关部分:</p> <pre> <code class="language-java">public class Thread implements Runnable { /** * Normal thread local values. */ ThreadLocal.Values localValues; } </code></pre> <p>接下来来看Values的定义, 了解其内部结构, 进一步清楚线程局部变量的存储细节:</p> <pre> <code class="language-java">/** * Per-thread map of ThreadLocal instances to values. */ static class Values { ... /** * Map entries. Contains alternating keys (ThreadLocal) and values. * The length is always a power of 2. */ private Object[] table; ... } </code></pre> <p>Values是用数组来存储ThreadLocal和对应的value的, 保存一个线程中不同ThreadLocal以及局部变量.Values类内部具体的细节, 推荐阅读 <a href="/misc/goto?guid=4959723474923463035" rel="nofollow,noindex">由浅入深全面剖析ThreadLocal</a> . 其实table数组中没有保存ThreadLocal的强引用, 而是ThreadLocal的reference变量, 实际上就是保存ThreadLocal的弱引用.</p> <pre> <code class="language-java">/** Weak reference to this thread local instance. */ private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this); </code></pre> <p>到这里就可以回答之前提到的两个问题, 线程局部变量是存储在Thread的localValues属性中, 以ThreadLocal的弱引用作为key, 线程局部变量作为value. 虽然每个线程共享同一个ThreadLocal对象, 但是线程局部变量都是存储在线程自己的成员变量中, 以此保持相互独立.</p> <p><strong>ThreadLocal的get方法的默认值</strong></p> <p>get方法就是取出Vaules中对应的线程局部变量, 需要注意的是在没有set的情况下, 调用get方法返回的默认值是null, 这其实是有initialValue方法确定的, 可以重写.</p> <pre> <code class="language-java">/** * Provides the initial value of this variable for the current thread. * The default implementation returns {@code null}. * * @return the initial value of the variable. */ protected T initialValue() { return null; } </code></pre> <h3><strong>Java中的ThreadLocal有什么区别</strong></h3> <p>Java的ThreadLocal源码与Android中的ThreadLocal不太一样, 不过大致的实现原理是一样的, Android中ThreadLocal稍微优化了一下, 更节约内存. 两者最大的区别就是存储局部变量的Values类在Java中是ThreadLocalMap类, 内部的存储方式有些不同, Java中用ThreadLocal.ThreadLocalMap.Entry来封装key和value.</p> <pre> <code class="language-java">static class ThreadLocalMap { private static final int INITIAL_CAPACITY = 16; private ThreadLocal.ThreadLocalMap.Entry[] table; private int size; private int threshold; ... static class Entry extends WeakReference<ThreadLocal> { Object value; Entry(ThreadLocal k, Object v) { super(k); this.value = v; } } } </code></pre> <p>两者都是以ThreadLocal弱引用作为key值.</p> <h3><strong>ThreadLocal的内存泄漏问题</strong></h3> <p>网上有讨论说ThreadLocal有可能出现内存泄漏问题, 这的确是有可能的.</p> <p>现在看下线程局部变量的引用链: Thread.localValues -> WeakReference 和 value. 如果没有其他对象引用ThreadLocal对象的话, ThreadLocal可能会被回收, 但是value不会被回收, value是强引用. 所以没有显式地调用remove的话, 的确有可能发生内存泄漏问题.</p> <p>不过ThreadLocal的设计者也考虑到这个问题, 在get或set方法中会检测key是否被回收, 如果是的话就将value设置为null, 具体是调用Values的cleanUp方法实现的. 这种设计可以避免多数内存泄漏问题, 但是极端情况下, ThreadLocal对象被回收后, 也没有调用get或set方法的话, 还是会发生内存泄漏.</p> <p>现在回过来看, 这种情况的发生都是基于没有调用remove方法, 而ThreadLocal的正确使用方式是在不需要的时候remove, 这样就不会出现内存泄漏的问题了.</p> <h3><strong>线程局部变量真的只能被一个线程访问?</strong></h3> <p>ThreadLocal的子类InheritableThreadLocal可以突破这个限制, 父线程的线程局部变量在创建子线程时会传递给子线程.</p> <p>看下面的示例, 子线程可以获得父线程的局部变量值:</p> <pre> <code class="language-java">private void testInheritableThreadLocal() { final ThreadLocal<String> threadLocal = new InheritableThreadLocal(); threadLocal.set("testStr"); Thread t = new Thread() { @Override public void run() { super.run(); Log.i(LOGTAG, "testInheritableThreadLocal = " + threadLocal.get()); } }; t.start(); } // 输出结果为 testInheritableThreadLocal = testStr </code></pre> <p>具体的实现逻辑:</p> <pre> <code class="language-java">public class Thread implements Runnable { ... /** * Normal thread local values. */ ThreadLocal.Values localValues; /** * Inheritable thread local values. */ ThreadLocal.Values inheritableValues; ... // 线程创建会调用的方法 private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) { ... // Transfer over InheritableThreadLocals. if (currentThread.inheritableValues != null) { inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues); } // add ourselves to our ThreadGroup of choice this.group.addThread(this); } } </code></pre> <h3><strong>使用建议</strong></h3> <ul> <li> <p>ThreadLocal变量本身定位为要被多个线程访问, 所以通常定义为static</p> </li> <li> <p>在线程池的情况下, 在ThreadLocal业务周期结束后, 最好显示地调用remove方法</p> </li> </ul> <p> </p> <p>来自:http://johnnyshieh.github.io/android/2016/11/02/explore-threadlocal/</p> <p> </p>