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. 【原则】常用windows开发 客户端工具 收集

    1.  Navicat Premium 推荐: mysql客户端, postgreSQL 客户端, Sqlite客户端 2.  robomongo 推荐:mongoDB客户端

  2. itunes connect提交app教程

    .打开itunes connect登陆之后,选择Manage Your Apps,再选Add New App: .填写项目相关信息,不知道怎么填的点击问号查看: Bundle ID Suffix需要和 ...

  3. 洛谷P1251 餐巾(网络流)

    P1251 餐巾 15通过 95提交 题目提供者该用户不存在 标签网络流贪心 难度提高+/省选- 提交该题 讨论 题解 记录 最新讨论 为什么我全部10个测试点都对… 题目描述 一个餐厅在相继的N天里 ...

  4. codevs4600 [NOI2015]程序自动分析==洛谷P1955 程序自动分析

    4600 [NOI2015]程序自动分析  时间限制: 1 s  空间限制: 256000 KB  题目等级 : 黄金 Gold 题解  查看运行结果     题目描述 Description 在实现 ...

  5. NodeJS学习之异步编程

    NodeJS -- 异步编程 NodeJS最大的卖点--事件机制和异步IO,对开发者并不透明 代码设计模式 异步编程有很多特有的代码设计模式,为了实现同样的功能,使用同步方式和异步方式编写代码会有很大 ...

  6. 揪出ie和Edge的js代码

    var userAgent = navigator.userAgent; var isIE = userAgent.indexOf("compatible") > -1 &a ...

  7. Nginx服务器不支持PATH_INFO的问题及解决办法

    最近在写一个小程序,然后里面自己写了个URL的处理器,比如说访问index.php/article 那么就会自动加载进来article页面,访问index.php/home就会自动加载home页面. ...

  8. PHP浮点数的精度

    在百度知道上看到这么一个问题 var_dump((0.3-0.2)==0.1); 结果是:false 后来查查手册,原来是浮点数的精度问题.那么0.3-0.2-0.1等于多少呢,结果:2.775557 ...

  9. MySQL之连接数据库的两种方法

    方法一: package DB; import java.sql.Connection; import java.sql.DriverManager; public class Conn { // 定 ...

  10. 继承关系在内存和DB中的映射

    使用 将若干相似的类映射为单表,对拥有许多特殊数据的类使用具体表继承. 对高层次使用类表继承,对低层次使用具体表继承. Single Table Inheritance 在DB中将类继承层次设计为一个 ...