深入理解ThreadLocal
来源:互联网 发布:黑暗之魂 隐藏 知乎 编辑:程序博客网 时间:2024/06/05 22:39
转载请注明出处:http://blog.csdn.net/fishle123/article/details/48087753
在Android里面,在不同的线程(假设子线程已经创建了Looper)中创建Handler的时候,并不需要显式指定Looper,系统能自动找到改线程自己的Looper。不同线程的Looper相互独立,之所以能做到这一点,就是借助ThreadLocal来实现的。下面结合源码来分析ThreadLocal的使用及实现原理。
1 ThreadLocal的使用
先来看一个ThreadLocal使用的例子:
public class ThreadLocalTest {static ThreadLocal mThreadLocal = new ThreadLocal<Long>();static long id=0;public ThreadLocalTest() {id = Thread.currentThread().getId();mThreadLocal.set(id);}public void printValue() {System.out.println("Thread " + Thread.currentThread().getId() + ":\t value=" + mThreadLocal.get() + "\t id=" + id);}static class PrintValueRunnable implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stubThreadLocalTest test = new ThreadLocalTest();test.printValue();}}public static void main(String[] args) {new Thread(new PrintValueRunnable()).start();new Thread(new PrintValueRunnable()).start();}}上面这段程序向我们展示了ThreadLocal的基本用法。这个例子很简单,定义了2个类变量mThreadLocal和id,一个成员方法printValue用来打印mThreadLocal和id的值,定义一个PrintValueRunnable在run方法中会先new一个ThreadLocalTest的实例,然后调用printValue来打印值。最后在main函数里面,创建两个线程,在线程里面执行PrintValueRunnable来执行打印任务。在ThreadLocalTest因为mThreadLocal和id都是类成员变量,所以在两个线程中都可以访问。在ThreadLocalTest的构造函数中会分别将线程id保存到mThreadLocal和id中。下面先看一下打印结果:
从打印结果可以看到:mThreadLocal中保存时每个线程的ID,但是id保存的是同一个ID值。因为id是两个进程内共享的,所以id的值是后一个执行的线程的id。mThreaLocal也是进程内共享的,但是mThreadLocal.get()能保证不同线程间调用mThreadLocal.set()进来的值互不干扰。所以,当每个线程是用ThreadLocal来保存Looper的时候,可以保证在任何时候任何地方创建的Handler都能正确的拿到当前线程的Looper。
那ThreadLocal是怎么做到这一点的呢?
2 ThreadLocal的实现原理分析
其实在Thread中有一个hash结构的ThreadLocal.ThreadLocalMap对象的实例threadLocals,threadLocals中才是真正保存变量的地方。ThreadLocal负责管理threadLocals中保存的变量。
下面结合ThreadLocal的源码来分析它的实现原理。先来看ThreadLocal的set和get方法:
public void set(T value) { Thread t = Thread.currentThread(); 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); }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(); }
可以看到,在set中以当前线程的实例为参数调用getMap获取当前线程的ThreadLocalMap对象,如果线程的threadLocals不为null,就将value保存到threadLocals中,反之,先创建一个ThreadLocalMap实例。ThreadLocal.get()也是从threadLocals中来读取value。从这儿也可以看出,我们想要隔离的value并不是保存到ThreadLocal中,而是在每个线程对象的内部来保存。因为是每个线程自己来保存value,所以做到了线程间相互隔离。
第5行代码需要我们注意,在set中保存value的时候,是以this即当前ThreadLocal实例为键,以待保存的变量value为值来保存的。
下面来看一下ThreadLocalMap的具体实现,先看构造函数:
/** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ 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); }
在构造函数中,先new一个Entry数组,默认大小INITIAL_CAPACITY为16。随着元素增多,会动态调整其大小,但都是2n。然后根据ThreadLocal.threadLocalHashCode来计算hash值,具体算法如第8行所示。再看一下Entry的定义:
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }
可以看到Entry继承与WeakReference,ThreadLocalMap中的键ThreadLocal对象通过软引用来保存,值则保存到一个Object的实例value中。
再来看一下调用set/get时,ThreadLocalMap是怎样查找对象的,通过前面的代码知道调用ThreadLoca.set的时候是通过调用ThreadLocalMap.set来完成的,先看set:
private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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)]) { ThreadLocal k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
在set中,先在第10行计算hash值,并作为在table中查找的起点。在查找的过程中,如果当前Entry的key与待插入的key相同,则直接更新value。如果找到一个空的Entry,则退出循环,然后插入value。通过nextIndex可以看到,如果插入的时候发生碰撞,那么采用线性探查来计算下一个位置。在查找的过程中,如果遇到key为null的Entry(因为key是一个软引用,所以每次gc之后就可能有key为null),如第22行所示,会调用replaceStaleEntry继续查找,如果没有更合适的,则插入到当前位置。插入完成如果有必要,会对hash表进行清理:删除掉key为null的Entry,并对受影响的部分进行重新hash。
再看一下ThradLocalMap的get操作:
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 Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
getEntry很简单,先计算hash值,然后比较entry中保存的key是否与待查找的key一致,如果是则直接返回,反之,按照线性探查法继续查找。查找的过程中,如果发现有Entry的key为null,则会调用expungeStaleEntry进行清理,并对受影响的部分重新hash。
到此为止,已经分析完ThradLocal的实现原理:
在Thread中有一个hash结构的ThreadLocal.ThreadLocalMap对象的成员变量threadLocals,threadLocals中以ThreadLocal对象为key,以要隔离的值为value(即调用threadLocal.set(value)中的value)。ThreadLocal负责管理threadLocals中保存的变量。
3 InheritableThreadLocal的使用
通过上面的分析知道通过ThreadLocal保存的值是线程隔离的。其实在Thread对象中,还有一个ThreadLocal.ThreadLocalMap类型的成员变量inheritableThreadLocals。inheritableThreadLocals其实是一个InheritableThreadLocals类型,InheritableThreadLocals是ThreadLocal的子类。保存在inheritableThreadLocals中的值可以传递给子线程。下面看一个例子:
public class InheritableThreadLocalTest {static InheritableThreadLocal<Long> mInheritableThreadLocal = new InheritableThreadLocal<Long>();static ThreadLocal<Long> mThreadLocal = new ThreadLocal<Long>();static SimpleInheritableThreadLocal<Long> mSimpleInheritableThreadLocal=new SimpleInheritableThreadLocal<Long>();public static void printValue() { System.out.println("Thread " + Thread.currentThread().getId() + ":\t valueFromParent=" + mInheritableThreadLocal.get() + "\t valueFromLocal="+ mThreadLocal.get() +"\tsimpleValueFromParent="+mSimpleInheritableThreadLocal.get());} static class PrintValueRunnable implements Runnable { @Overridepublic void run() {// TODO Auto-generated method stubInheritableThreadLocalTest.printValue();if (Thread.currentThread().getId() % 2 == 0) {mInheritableThreadLocal.set(mInheritableThreadLocal.get()+1);}InheritableThreadLocalTest.printValue();}} public static void main(String[] args) { long tid = Thread.currentThread().getId();mInheritableThreadLocal.set(tid);mThreadLocal.set(tid);mSimpleInheritableThreadLocal.set(tid);System.out.println("mainThread: " + "\t valueFromLocal=" + mThreadLocal.get());new Thread(new PrintValueRunnable()).start();new Thread(new PrintValueRunnable()).start();new Thread(new PrintValueRunnable()).start();}}
打印结果如下:
在这个例子,先在主线程中new一个InheritableThreadLocal对象,然后在子线程中访问。通过打印结果可以看到,主线程设置到InheritableThreadLocal中的值会复制给子线程,然后父子线程可以各自独立读写保存在InheritableThreadLocal中的值。可以还有人注意到了,在上面的例子中还有一个SimpleInheritableThreadLocal对象,这个是我自定义,它继承了InheritableThreadLocal,并重写了InheritableThreadLocal.childValue。我们可以通过重写childValue来控制从父线程中继承过来的value的初始值。在SimpleInheritableThreadLocal中会对从父线程继承的Long对象做加1处理。默认情况下,是不做任何处理的。SimpleInheritableThreadLocal的代码如下:
public class SimpleInheritableThreadLocal<T> extends InheritableThreadLocal<T> { @Overrideprotected T childValue(T parentValue) {// TODO Auto-generated method stubif (parentValue instanceof Long) {Long res = (Long) parentValue + 1;return (T) res;} return super.childValue(parentValue);}}
可能有人会有疑问:ThreadLocal是线程隔离的,而InheritableThreadLocal又是ThreadLocal的子类,所以InheritableThreadLocal也应该是线程隔离的。那这里是怎么做到从父线程继承值呢?子线想一想:实现线程隔离的核心思想是将要隔离的变量保存到Thread对象里面,然后通过ThreadLocal来读写这个变量;其实不通过ThreadLocal,在Thread内部是可以直接读写threadLocals的。那既然inheritableThreadLocals是Thread的成员变量,那么在初始化Thread的时候就可以直接读取父线程的inheritableThreadLocals并把它保存的values设置为子线程的inheritableThreadLocals初始值。
这个想法对不对呢?我们来看一下Thread初始化部分的源码:
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0);}private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name.toCharArray(); Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID();}
先看Thread的构造函数,这里调用了init函数,最终会调用这init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc)函数。直接看第60行,如果父线程的parent.inheritableThreadLocals不为null,就会以父线程的inheritableThreadLocals为参数来构造子线程的inheritableThreadLocals。我们再看一下createInheritedMap函数:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap);} private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { ThreadLocal key = e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
到这里就可以看到:在ThreadLocalMap的构造函数中会遍历保存父线程的inheritableThreadLocals中键值对,并且在读取value的会调用key.childValue计算子线程中的value。所以在SimpleInheritableThreadLocal重写了childValue来根据父线程的value计算子线程的value,默认情况是不做任何处理,直接返回的,如:
T childValue(T parentValue) { throw new UnsupportedOperationException(); }
总结
到此为止,本文介绍了ThreadLocal的使用,及其实现原理。最后还介绍了InheritThreadLocal的使用。在Android中,ThreadLocal使用的一个例子就是Looper,有兴趣的可以看一下Looper的实现原理。或者参考另一篇对Looper分析的blog:Android消息机制分析
- 深入理解ThreadLocal
- ThreadLocal深入理解
- ThreadLocal深入理解2
- ThreadLocal深入理解
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- ThreadLocal深入理解
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- ThreadLocal深入理解 修订版
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 【Java】深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 整合spark和hive
- 对象的简单内存分析
- 黑马程序员--Java_IO流(二)
- UINavigationController及界面传值
- [cocoa]_[初级]_[解析XML文件获取数据]
- 深入理解ThreadLocal
- 使用Toolbar + DrawerLayout快速实现高大上菜单侧滑
- mysql登录报错“Access denied for user 'root'@'localhost' (using password: YES”的处理方法
- DIV 边框重叠 像素变粗 解决办法
- Kth Largest Element in an Array
- 1041. Be Unique (20)
- 极速前进
- 斐波那契数列
- POSIX多线程--线程基本操作接口