理解Android中ThreadLocal的工作原理
2016-11-11 18:13
761 查看
一提Android中的消息机制,我们很容易就想到Handler,确实,Android中的异步消息处理机制,主要都依赖于Handler机制(不懂的可以看深入理解Android中的Handler机制 这篇文章),而Handler机制的实现其实和ThreadLocal密不可分。
以上是官方文档对ThreadLocal的解释,从这里我们可以看出,ThreadLocal相当于一个容器,存储着每个线程的数据,且所有线程都共享这一个ThreadLocal变量,但每个线程访问该对象时会获得不同的数据,而且就算修改这些数据也不会影响到其他线程,即所有线程互不干扰。因为这个特性,ThreadLocal在Java中也常用于处理多线程,也就是说,ThreadLocal和线程同步机制一样,都是为了解决多线程访问同一变量造成的访问冲突问题,不同的的是,
同步机制是通过锁机制牺牲时间来达到多线程安全访问的目的;
ThreadLocal为每个线程提供独立的变量副本,牺牲空间保证每个线程访问相同变量时可以得到自己专属的变量,达到多线程安全访问的目的。
具体如何实现的呢?我们从Android中的Handler机制进行讲解。
在Handler机制中,我们需要新建一个线程,而在每个Thread中,都会包含一个ThreadLocal.Values 类型的localValues变量,表示该线程映射到ThreadLocal中的值。
这里主要是对几个变量进行初始化初始化。包括用于存储线程本身一些数据的Object数组table,该数组也存储了线程间共享的ThreadLocal对象和自身相关的Looper 对象,还有用于将哈希值转为指数的掩码。
记住该Values类后我们来看ThreadLocal是如何在消息机制中发挥作用的。
在Handler机制中,
Handler用于处理子线程耗时操作结束后的反馈,一般用于从子线程切换回主线程并更新UI。
MessageQueue用于存储消息。
Looper用于处理消息,以无限循环的形式查找MessageQueue中是否有消息。
所以每个线程的运行都需要Looper的存在,不然无法处理消息(不懂的先完看深入理解Android中的Handler机制 这篇文章)。
所以先讲下调用Looper的构建。通常我们都是通过Looper.prepare()来构建looper实例的,但是该方法除了实例一个looper并创建一个MessageQueue、指定其mThread为当前线程外,就是将该Looper对象存到ThreadLocal中。
所以我们来看一下ThreadLocal的set方法,如下:
这里
从上面代码可以看到,该方法其实就是把传进来的ThreadLocal对象和looper对象存到它的table数组中,可以看到,这里通过计算该threadLocal的哈希值
到这里Looper构造并准备完毕。
然后就应该构造Handler获取looper了。在创建handler时,若参数中没有指定looper,会通过
那么具体是如何获得当前线程相关的looper的呢?
可以看到,这里依然是通过当前线程自身的localValues这个值内部的table数组来获得相关联的looper对象,如果是null,则重新初始化该Values对象,如果不为null则返回table数组中该ThreadLocal对象的引用所在位置的下一个值,也就是我们前面set进去looper啦。
综上,我们可以知道,在handler机制中,之所以每个线程能够获得自身的looper,而不影响其他线程,是因为通过操作每个线程自身的localValues对象内部的table数组实现的,从而达到不同线程访问同一个threadLocal的get()和set()不会访问或修改到其他线程的数据。
ThreadLocal
Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same {@code ThreadLocal} object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports {@code null} values.以上是官方文档对ThreadLocal的解释,从这里我们可以看出,ThreadLocal相当于一个容器,存储着每个线程的数据,且所有线程都共享这一个ThreadLocal变量,但每个线程访问该对象时会获得不同的数据,而且就算修改这些数据也不会影响到其他线程,即所有线程互不干扰。因为这个特性,ThreadLocal在Java中也常用于处理多线程,也就是说,ThreadLocal和线程同步机制一样,都是为了解决多线程访问同一变量造成的访问冲突问题,不同的的是,
同步机制是通过锁机制牺牲时间来达到多线程安全访问的目的;
ThreadLocal为每个线程提供独立的变量副本,牺牲空间保证每个线程访问相同变量时可以得到自己专属的变量,达到多线程安全访问的目的。
具体如何实现的呢?我们从Android中的Handler机制进行讲解。
在Handler机制中,我们需要新建一个线程,而在每个Thread中,都会包含一个ThreadLocal.Values 类型的localValues变量,表示该线程映射到ThreadLocal中的值。
ThreadLocal.Values
首先来看一下它的一个和消息机制相关的构造函数。/** Size must always be a power of 2. */ private static final int INITIAL_SIZE = 16; /** Placeholder for deleted entries. */ private static final Object TOMBSTONE = new Object(); /** * Map entries. Contains alternating keys (ThreadLocal) and values. * The length is always a power of 2. */ private Object[] table; /** Used to turn hashes into indices. */ private int mask; /** Number of live entries. */ private int size; /** Number of tombstones. */ private int tombstones; /** Maximum number of live entries and tombstones. */ private int maximumLoad; /** Points to the next cell to clean up. */ private int clean; Values() { initializeTable(INITIAL_SIZE); this.size = 0; this.tombstones = 0; } private void initializeTable(int capacity) { this.table = new Object[capacity * 2]; this.mask = table.length - 1; this.clean = 0; this.maximumLoad = capacity * 2 / 3; // 2/3 }
这里主要是对几个变量进行初始化初始化。包括用于存储线程本身一些数据的Object数组table,该数组也存储了线程间共享的ThreadLocal对象和自身相关的Looper 对象,还有用于将哈希值转为指数的掩码。
记住该Values类后我们来看ThreadLocal是如何在消息机制中发挥作用的。
在Handler机制中,
Handler用于处理子线程耗时操作结束后的反馈,一般用于从子线程切换回主线程并更新UI。
MessageQueue用于存储消息。
Looper用于处理消息,以无限循环的形式查找MessageQueue中是否有消息。
所以每个线程的运行都需要Looper的存在,不然无法处理消息(不懂的先完看深入理解Android中的Handler机制 这篇文章)。
所以先讲下调用Looper的构建。通常我们都是通过Looper.prepare()来构建looper实例的,但是该方法除了实例一个looper并创建一个MessageQueue、指定其mThread为当前线程外,就是将该Looper对象存到ThreadLocal中。
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
所以我们来看一下ThreadLocal的set方法,如下:
public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); } Values values(Thread current) { return current.localValues; } Values initializeValues(Thread current) { return current.localValues = new Values(); }
这里
values(currentThread)方法返回的就是我们前面提到的ThreadLocal.Values对象,也就是Thread类中的localValue值,如果是null就进行初始化,初始化执行的就是我们前面提到的构造方法。初始化成功后便将该ThreadLocal对象和该looper对象(即参数value)存储到该values对象中,具体实现如下:
void put(ThreadLocal<?> key, Object value) { // Cleans up after garbage-collected thread locals. cleanUp(); // Keep track of first tombstone. That's where we want to go back // and add an entry if necessary. int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } if (k == null) { if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; } // Go back and replace first tombstone. table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } // Remember first tombstone. if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } } private int next(int index) { return (index + 2) & mask; }
从上面代码可以看到,该方法其实就是把传进来的ThreadLocal对象和looper对象存到它的table数组中,可以看到,这里通过计算该threadLocal的哈希值
key.hash与掩码值
mask的与运算结果来判断table中是否存在该threadLocal对象的引用
reference,若存在则将该looper对象放在该引用在table数组中的位置的后一个位置,即table[index + 1];若不存在则将该threadLocal对象的引用存到table数组的[index]位置上,而looper对象同样存在其后一个位置。也就是说,looper的值都会存在threadLocal引用的后一个位置。
到这里Looper构造并准备完毕。
然后就应该构造Handler获取looper了。在创建handler时,若参数中没有指定looper,会通过
mLooper = Looper.myLooper();为自身指定一个looper对象。而myLooper实际上就是返回threadLocal.get()而已。
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
那么具体是如何获得当前线程相关的looper的呢?
public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); } Values values(Thread current) { return current.localValues; } Values initializeValues(Thread current) { return current.localValues = new Values(); }
可以看到,这里依然是通过当前线程自身的localValues这个值内部的table数组来获得相关联的looper对象,如果是null,则重新初始化该Values对象,如果不为null则返回table数组中该ThreadLocal对象的引用所在位置的下一个值,也就是我们前面set进去looper啦。
综上,我们可以知道,在handler机制中,之所以每个线程能够获得自身的looper,而不影响其他线程,是因为通过操作每个线程自身的localValues对象内部的table数组实现的,从而达到不同线程访问同一个threadLocal的get()和set()不会访问或修改到其他线程的数据。
相关文章推荐
- Android ListView工作原理完全解析,带你从源码的角度彻底理解,androidlistview
- Android开发中通过源码彻底理解ListView工作原理【超详细】
- Android 厉害了ThreadLocal的工作原理和实例分析
- Android的消息机制之ThreadLocal的工作原理
- Android ListView工作原理完全解析,带你从源码的角度彻底理解
- 【Android消息处理机制】正确理解ThreadLocal(一)
- Android中ThreadLocal的工作原理
- Android ThreadLocal理解
- Android的消息机制之ThreadLocal的工作原理
- 深入理解ThreadLocal工作原理及使用示例
- Android消息机制之ThreadLocal的工作原理
- Android ListView工作原理完全解析,带你从源码的角度彻底理解
- Android ListView工作原理完全解析,带你从源码的角度彻底理解
- Android ThreadLocal理解--续篇
- Android ListView工作原理完全解析,带你从源码的角度彻底理解
- Android的消息机制之ThreadLocal的工作原理
- Android的消息机制之ThreadLocal的工作原理
- 深入理解----ThreadLocal的工作原理
- Android 消息机制之ThreadLocal的工作原理
- 彻底理解引用在 Android 和 Java 中的工作原理