深入分析ThreadLocal

执著梦飞 7年前
   <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>