正确解读ThreadLocal
并发的学习与使用系列 第七篇
在Android的消息机制中,Handler是非常重要的一部分,而完全要理解Handler的机制,首先应该理解ThreadLocal,关于ThreadLocal,见到很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多人都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,这样的词容易让人产生误解或者迷惑。
首先,从最新的ThreadLocal源码看,ThreadLocal并未创建任何本地变量,也没有copy副本的存在,是直接用的Thread对象的成员变量,因此叫做“线程变量帮助类”其实更合适,它的作用就是拿到当前线程对象的Object[] value数组,然后进行存储和取值,因为这属于每个线程的内部变量数组,因此也不存在共享,所以也就没有线程安全的问题。
先看一个例子:
例子可以看出不同的线程得到的值是不同的,说明ThreadLocal可以使同一个变量在不同的线程里有不同的值,为什么同一个变量在不同的线程的会表现出不同的值呢,源码说明一切:
先看set方法:
ThreadLocal.set():
|
|
ThreadLocal.values():
|
|
每个线程里有一个Values对象,对象有个Object[]的数组table,这个Object[]数组就是用来存储属于当前线程的内容,这也是为什么叫ThreadLocal的原因,可以看出ThreadLocal的Values引用直接指向Thread的localValues值。set值时实际上是将T value直接存入每个线程的数组里。
同时为了避免内存泄露问题,ThreadLocal通过弱引用所持有。
|
|
看下put()方法的实现。
Values.put():
简化后的代码:
|
|
结合注释很好理解,存储过程可以简单看做用单个数组来实现的简易HashMap过程,HashMap的key是当前ThreadLocal对象的hash值与当前数组长度的求模运算,存入在数组的index位置,value就是当前的存入值,这个值总是放在index+1的位置,可以理解为index和index+1这两个位置就是HashMap的Entry。在jdk1.7之前就是用HashMap来实现的,原理都是一样的。这样是Thread类更加的轻量化。
ThreadLocal.get():
|
|
通过上面的分析get函数也很好理解了。先得到当前线程对象的Values对象,然后得到Values中的Object[] table数组,从数组中取出值。
ThreadLocal 实例通常建议是用 private static 字段,至于原因想不太清楚。但这不是绝对的,在Android的事件机制Looper中
|
|
这就不是一个private变量,至于静态
|
|
因为需要静态方法获取Looper对象,所以就必须是静态的的吧。看到一种说法是设置static 是因为ThreadLocal支持线程范围生命周期的变量,所以不属于类的属性。不知是否有些牵强。
关于内存泄露的问题
因为是Values数组持有的是ThreadLocal的若引用,所以不会存在内存泄露的问题。但确定不需要使用的时候最好调用remove()方法来释放内存。
对比总结
判断是否需要对资源进行同步的判断准则是,当前获取(get)资源是否会有其他线程进行修改(set)或者当前进行修改的资源是否会有其他线程可以获取。几种并发工具类的关键点:
- synchronized——主要是进行同步的控制,同时保证可见性。
- volatile——阻止指令重排序,保证可见性。
- ThreadLocal——线程空间内的全局变量,不存在同步问题。