Android里,在不同的线程(假设子线程已经创建了Looper)中创建Handler时,并不需要显式指定Looper,系统能自动找到该线程自己的Looper。不同线程的Looper相互独立,之所以能做到这一点,就是借助ThreadLocal来实现的。下面结合源码来分析ThreadLocal的使用及实现原理。

1 ThreadLocal的使用

先来看一个ThreadLocal使用的例子:

[java] view plain copy

  1. public class ThreadLocalTest {

  2. static ThreadLocal mThreadLocal = new ThreadLocal<Long>();

  3. static long id=0;

  4. public ThreadLocalTest() {

  5. id = Thread.currentThread().getId();

  6. mThreadLocal.set(id);

  7. }

  8. public void printValue() {

  9. System.out.println("Thread " + Thread.currentThread().getId() + ":\t value=" + mThreadLocal.get() + "\t id=" + id);

  10. }

  11. static class PrintValueRunnable implements Runnable {

  12. @Override

  13. public void run() {

  14. // TODO Auto-generated method stub

  15. ThreadLocalTest test = new ThreadLocalTest();

  16. test.printValue();

  17. }

  18. }

  19. public static void main(String[] args) {

  20. new Thread(new PrintValueRunnable()).start();

  21. new Thread(new PrintValueRunnable()).start();

  22. }

  23. }

上面代码展示了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方法:

[java] view plain copy

  1. public void set(T value) {

  2. Thread t = Thread.currentThread();

  3. ThreadLocalMap map = getMap(t);

  4. if (map != null)

  5. map.set(this, value);

  6. else

  7. createMap(t, value);

  8. }

  9. ThreadLocalMap getMap(Thread t) {

  10. return t.threadLocals;

  11. }

  12. void createMap(Thread t, T firstValue) {

  13. t.threadLocals = new ThreadLocalMap(this, firstValue);

  14. }

  15. public T get() {

  16. Thread t = Thread.currentThread();

  17. ThreadLocalMap map = getMap(t);

  18. if (map != null) {

  19. ThreadLocalMap.Entry e = map.getEntry(this);

  20. if (e != null)

  21. return (T)e.value;

  22. }

  23. return setInitialValue();

  24. }

可以看到,在set中以当前线程的实例为参数调用getMap获取当前线程的ThreadLocalMap对象,如果线程的threadLocals不为null,就将value保存到threadLocals中,反之,先创建一个ThreadLocalMap实例。ThreadLocal.get()也是从threadLocals中来读取value。从这儿也可以看出,我们想要隔离的value并不是保存到ThreadLocal中,而是在每个线程对象的内部来保存。因为是每个线程自己来保存value,所以做到了线程间相互隔离。

第5行代码需要我们注意,在set中保存value的时候,是以this即当前ThreadLocal实例为键,以待保存的变量value为值来保存的。

下面看下ThreadLocalMap的具体实现,先看构造函数:

[java] view plain copy

  1. /**

  2. * Construct a new map initially containing (firstKey, firstValue).

  3. * ThreadLocalMaps are constructed lazily, so we only create

  4. * one when we have at least one entry to put in it.

  5. */

  6. ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {

  7. table = new Entry[INITIAL_CAPACITY];

  8. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

  9. table[i] = new Entry(firstKey, firstValue);

  10. size = 1;

  11. setThreshold(INITIAL_CAPACITY);

  12. }

在构造函数中,先new一个Entry数组,默认大小INITIAL_CAPACITY为16。随着元素增多,会动态调整其大小,但都是2n。然后根据ThreadLocal.threadLocalHashCode来计算hash值,具体算法如第8行所示。再看一下Entry的定义:

[java] view plain copy

  1. static class Entry extends WeakReference<ThreadLocal> {

  2. /** The value associated with this ThreadLocal. */

  3. Object value;

  4. Entry(ThreadLocal k, Object v) {

  5. super(k);

  6. value = v;

  7. }

  8. }

可以看到Entry继承与WeakReference,ThreadLocalMap中的键ThreadLocal对象通过软引用来保存,值则保存到一个Object的实例value中。

再来看一下调用set/get时,ThreadLocalMap是怎样查找对象的,通过前面的代码知道调用ThreadLoca.set的时候是通过调用ThreadLocalMap.set来完成的,先看set:

[java] view plain copy

  1. private void set(ThreadLocal key, Object value) {

  2. // We don't use a fast path as with get() because it is at

  3. // least as common to use set() to create new entries as

  4. // it is to replace existing ones, in which case, a fast

  5. // path would fail more often than not.

  6. Entry[] tab = table;

  7. int len = tab.length;

  8. int i = key.threadLocalHashCode & (len-1);

  9. for (Entry e = tab[i];

  10. e != null;

  11. e = tab[i = nextIndex(i, len)]) {

  12. ThreadLocal k = e.get();

  13. if (k == key) {

  14. e.value = value;

  15. return;

  16. }

  17. if (k == null) {

  18. replaceStaleEntry(key, value, i);

  19. return;

  20. }

  21. }

  22. tab[i] = new Entry(key, value);

  23. int sz = ++size;

  24. if (!cleanSomeSlots(i, sz) && sz >= threshold)

  25. rehash();

  26. }

  27. private static int nextIndex(int i, int len) {

  28. return ((i + 1 < len) ? i + 1 : 0);

  29. }

在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操作:

[java] view plain copy

  1. private Entry getEntry(ThreadLocal key) {

  2. int i = key.threadLocalHashCode & (table.length - 1);

  3. Entry e = table[i];

  4. if (e != null && e.get() == key)

  5. return e;

  6. else

  7. return getEntryAfterMiss(key, i, e);

  8. }

  9. private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {

  10. Entry[] tab = table;

  11. int len = tab.length;

  12. while (e != null) {

  13. ThreadLocal k = e.get();

  14. if (k == key)

  15. return e;

  16. if (k == null)

  17. expungeStaleEntry(i);

  18. else

  19. i = nextIndex(i, len);

  20. e = tab[i];

  21. }

  22. return null;

  23. }

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中保存的变量。

深入理解ThreadLocal(一)的更多相关文章

  1. 【Java】深入理解ThreadLocal

    一.前言 要理解ThreadLocal,首先必须理解线程安全.线程可以看做是一个具有一定独立功能的处理过程,它是比进程更细度的单位.当程序以单线程运行的时候,我们不需要考虑线程安全.然而当一个进程中包 ...

  2. 理解ThreadLocal背后的概念

    介绍 我之前在任何场合都没有使用过thread local,因此没有注意到它,直到最近用到它的时候. 前提信息 线程可以理解为一个单独的进程,它有自己的调用栈.在java中每一个线程都有一个调用栈或者 ...

  3. 简单理解ThreadLocal原理和适用场景

    https://blog.csdn.net/qq_36632687/article/details/79551828?utm_source=blogkpcl2 参考文章: 正确理解ThreadLoca ...

  4. 理解ThreadLocal(之二)

    想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码 ...

  5. 理解ThreadLocal(之一)

    ThreadLocal是什么 在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编 ...

  6. 正确理解ThreadLocal

    想必很多朋友对 ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理 解,然后根据ThreadLocal类的 ...

  7. 彻底理解ThreadLocal一

    synchronized这类线程同步的机制可以解决多线程并发问题,在这种解决方案下,多个线程访问到的,都是同一份变量的内容.为了防止在多线程访问的过程中,可能会出现的并发错误.不得不对多个线程的访问进 ...

  8. 彻底理解ThreadLocal

    ThreadLocal是什么 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地 ...

  9. 深入理解ThreadLocal

    ThreadLocal是什么 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地 ...

  10. 深入理解ThreadLocal(转)(2015年06月11日)

    注明:转自:http://my.oschina.net/clopopo/blog/149368 学习一个东西首先要知道为什么要引入它,就是我们能用它来干什么.所以我们先来看看ThreadLocal对我 ...

随机推荐

  1. revel + swagger 文档也能互动啦

    beego 从 1.3 后开始支持自动化API文档,不过,目测比较复杂,一直期望 revel 能有官方支持. revel 确实已经有了官方支持的计划,有可能将在 0.14 版本支持,现在才 0.11. ...

  2. css隐藏元素display:none,opacity:0;filter:alpha(opacity=0-100;,visibility:hidden的区别

    在CSS中,让元素隐藏(指屏幕范围内肉眼不可见)的方法很多,有的占据空间,有的不占据空间:有的可以响应点击,有的不能响应点击.我们一般有三种方式:display:none, opacity:0;fil ...

  3. 让文字在标签li的底部

    <ul> {aspcms:navlist type=6 } <li style="border-bottom:1px solid #d4d4d4;padding-top:2 ...

  4. ASP.Net 类(CS)文件怎样获取Web应用程序的路径

    Web应用程序,写了一个线程CS类别,这个类别将会放于Global.asax文件中执行,主要是监控程序下某一个文件是否有异动,而作出相应警示动作,如发送邮件等. 实现运行过程中,也许会有一个情况出现, ...

  5. Java中浮点数能连续精确表示整数的范围

    转自http://blog.csdn.net/seizef/article/details/5571783#ref_1,有删改. 先简单介绍一下浮点数在计算机中的组成,在Java中采用的浮点数表示法是 ...

  6. 胸腺嘧啶“T”

    4.下列物质或结构中含胸腺嘧啶“T”的是( )A.DNA聚合酶 B.烟草花叶病毒C.ATP D.B淋巴细胞中的线粒体

  7. 【风马一族_git_github】github项目建成网站

    ---------------------------------------- 1)点击 Settings 2)上一个红色矩形是访问的网址,下一个红色矩形是用来设置页面相关的信息 3)页面信息的修改 ...

  8. log tag

        Media Player MediaPlayerService     NuPlayerDecoder     NuPlayer      MediaFocusControl       Ac ...

  9. IE8浏览器跨域接口访问异常的解决办法

    IE8版本以下浏览器绝对是一个神奇的存在,忙碌好久,万事具备,居然在ajax调用接口的时候直接爆炸 陈述一下问题 首先是有这样一个接口,请求类型POST,入参JSON,出参JSON,jQuery aj ...

  10. 用canvas 绘制的饼状统计图、柱状统计图、折线统计图

    canvas 绘制的饼状统计图 canvas 绘制的柱状统计图 canvas 绘制的折线统计图