欢迎转载,但请务必在明确位置注明文章出处! http://johnnyshieh.github.io/android/2016/11/02/explore-threadlocal/
这篇文章主要分析Android中的ThreadLocal原理以及相关问题, 也分析与其在Java中内部实现的区别, 让大家理解ThreadLocal的使用场景与正确使用方法.
ThreadLocal的定义
Android源码中描述:
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变量.
从上面的定义看出, 关键的地方即: ThreadLocal对象是多线程共享的, 但每个线程持有自己的线程局部变量. ThreadLocal不是用来解决共享对象问题的, 而是提供线程局部变量, 让线程之间不会互相干扰.
下面看在Android中Looper的应用, 每个线程只有一个Looper对象:
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(); }
在了解ThreadLocal的作用后, 也会产生一些疑问:
线程局部变量是怎么存储的?
是怎么做到线程间相互独立的?
接下来在分析Android的ThreadLocal源码的过程中, 理解其实现原理, 并解决上面的疑问.
ThreadLocal的实现原理
ThreadLocal有三个主要的public方法: set, get, remove.
ThreadLocal是通过set方法存储局部变量的, 所以先从set方法看起:
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(); }
set方法中根据当前线程获得 Values
, 线程局部变量也是存储在 Values
中, 而不是ThreadLocal对象中. 如果一开始values为null, 就通过initializeValues方法初始化. 上面代码根据线程获得的 values
变量就是Thread对象的localValues变量, 可看下Thread源码中相关部分:
public class Thread implements Runnable { /** * Normal thread local values. */ ThreadLocal.Values localValues; }
接下来来看Values的定义, 了解其内部结构, 进一步清楚线程局部变量的存储细节:
/** * 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; ... }
Values是用数组来存储ThreadLocal和对应的value的, 保存一个线程中不同ThreadLocal以及局部变量.Values类内部具体的细节, 推荐阅读 由浅入深全面剖析ThreadLocal . 其实table数组中没有保存ThreadLocal的强引用, 而是ThreadLocal的reference变量, 实际上就是保存ThreadLocal的弱引用.
/** Weak reference to this thread local instance. */ private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);
到这里就可以回答之前提到的两个问题, 线程局部变量是存储在Thread的localValues属性中, 以ThreadLocal的弱引用作为key, 线程局部变量作为value. 虽然每个线程共享同一个ThreadLocal对象, 但是线程局部变量都是存储在线程自己的成员变量中, 以此保持相互独立.
ThreadLocal的get方法的默认值
get方法就是取出Vaules中对应的线程局部变量, 需要注意的是在没有set的情况下, 调用get方法返回的默认值是null, 这其实是有initialValue方法确定的, 可以重写.
/** * 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; }
Java中的ThreadLocal有什么区别
Java的ThreadLocal源码与Android中的ThreadLocal不太一样, 不过大致的实现原理是一样的, Android中ThreadLocal稍微优化了一下, 更节约内存. 两者最大的区别就是存储局部变量的Values类在Java中是ThreadLocalMap类, 内部的存储方式有些不同, Java中用ThreadLocal.ThreadLocalMap.Entry来封装key和value.
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; } } }
两者都是以ThreadLocal弱引用作为key值.
ThreadLocal的内存泄漏问题
网上有讨论说ThreadLocal有可能出现内存泄漏问题, 这的确是有可能的.
现在看下线程局部变量的引用链: Thread.localValues -> WeakReference
不过ThreadLocal的设计者也考虑到这个问题, 在get或set方法中会检测key是否被回收, 如果是的话就将value设置为null, 具体是调用Values的cleanUp方法实现的. 这种设计可以避免多数内存泄漏问题, 但是极端情况下, ThreadLocal对象被回收后, 也没有调用get或set方法的话, 还是会发生内存泄漏.
现在回过来看, 这种情况的发生都是基于没有调用remove方法, 而ThreadLocal的正确使用方式是在不需要的时候remove, 这样就不会出现内存泄漏的问题了.
线程局部变量真的只能被一个线程访问?
ThreadLocal的子类InheritableThreadLocal可以突破这个限制, 父线程的线程局部变量在创建子线程时会传递给子线程.
看下面的示例, 子线程可以获得父线程的局部变量值:
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
具体的实现逻辑:
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); } }
使用建议
-
ThreadLocal变量本身定位为要被多个线程访问, 所以通常定义为static
-
在线程池的情况下, 在ThreadLocal业务周期结束后, 最好显示地调用remove方法