-
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
-
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
从Handler中进入ThreadLocal的世界
-
Android中一个线程只能有一个Looper,如果一个线程已经有Looper,我们再在线程中调用Looper.prepare()方法会抛出
RuntimeException("Only one Looper may be created per thread")
。那如何确保一个线程中最多只能有一个Looper呢?我们从Looper中寻找答案。public final class Looper { static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //如果该线程已经有Looper if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //该线程中没有Looper sThreadLocal.set(new Looper(quitAllowed)); } ... }
从上面的源码我们能知道如果一个线程已经有Looper了
sThreadLocal.get()
便不为空就抛出异常。否则就会新建一个Looper然后set进入sThreadLocal。
ThreadLocal
-
sThreadLocal是一个静态的成员变量,所有线程共享它。那它是如何实现同一个静态变量在不同的线程中调用
get()
方法却能返回不同值的骚操作的呢?让我们来看看ThreadLocal的get()
和set()
public class ThreadLocal<T> { public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); //获取Thread中的成员变量ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } private T setInitialValue() { //initialValue()返回null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } ... }
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }
在每个Thread中都有一个ThreadLocalMap成员变量,ThreadLocal中的
get()
和set()
方法都是通过获取到当前线程的引用后直接再通过getMap()
方法拿到ThreadMap的引用。ThreadLocal就是通过能获取到每个线程中的ThreadLocalMap,从而实现线程间的数据隔离。ThreadLocalMap只能通过ThreadLocal的createMap()
方法初始化。就让我们来看看ThreadLocalMap。
ThreadLocalMap
-
ThreadLocalMap是ThreadLocal的内部类。它用来存储数据,采用类似hashmap机制,存储了以ThreadLocal为key,需要隔离的数据为value的Entry键值对数组结构。里面有一些具体关于如何清理过期的数据、扩容等机制,思路基本和hashmap差不多,有兴趣的可以自行阅读了解。
static class ThreadLocalMap { ... //存储放入的数据 private Entry[] table; //Entry继承ThreadLocal的弱引用 static class Entry extends WeakReference<ThreadLocal> { //要存储的变量 Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } private Entry getEntry(ThreadLocal key) { //获取存储数据的位置 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } private void set(ThreadLocal key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //遍历整个Entry数组 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { //覆盖数据 e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } //如果位置为空,就新建有要存储的Entry后放入数组 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } ... }
-
ThreadLocal实例被线程的ThreadLocalMap实例持有,也可以看成被线程持有。但是ThreadLocalMap的key是ThreadLocal实例的弱引用。从而避免了内存泄漏。
总结
- 每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。每个Thread只访问自己的 Map,那就不存在多线程写的问题,也就不需要锁。
- 与同步机制比较:对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
- 在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
网友评论