Android的Looper实现中核心用到了ThreadLocal这个类,但是基于对这个类不甚了解,所以详细看了源码和参考了一些博客,下面谈下自己的理解
源码参考:Android源码的java.lang.ThreadLocal类(这里边实现同jdk关于ThreadLocal的实现有所区别)
首先可以看下关于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对象,但是访问这个local对象时有自己单独的值,支持空值
*
* @see java.lang.Thread
* @author Bob Lee
*/
public
class ThreadLocal<T> {
...
}
从这段注释可以看出,ThreadLocal解决的不是多线程共享资源同步的问题,而是多线程访问独立的相同类型的变量的频繁存取和参数传递的复杂性问题
ThreadLocal由于泛型特性,支持读取任意类型的变量ThreadLocal的变量存储
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();
}
通过以上方法实现了用ThreadLocal存储当前线程指定变量的功能
ThreadLocal的的变量读取
public T get() {
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的一些核心方法
Values定义的变量
static class Values {
/**
* Size must always be a power of 2.
* table的初始容量 必须是2的n次方
*/
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.
* 存储变量的数组(模拟map),包含ThreadLocal为key和对应的值,
* 数组长度总是2的n次方
* table包含key数据类型为null、tombstones、references
*/
private Object[] table;
/** Used to turn hashes into indices.
* 计算下标的掩码,它的值是table的长度-1
*/
private int mask;
/** Number of live entries.
* 存放进来的实体的数量
*/
private int size;
/** Number of tombstones.
* 被删除的实体的数量
*/
private int tombstones;
/** Maximum number of live entries and tombstones.
* 用来判断是否需要进行rehash的阈值
*/
private int maximumLoad;
/** Points to the next cell to clean up.
* 下一个要进行清理的位置点
*/
private int clean;
}
添加数据到Values
/**
* Adds an entry during rehashing. Compared to put(), this method
* doesn't have to clean up, check for existing entries, account for
* tombstones, etc.
*/
void add(ThreadLocal<?> key, Object value) {
for (
int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k ==
null) {
table[index] = key.reference;
table[index +
1] = value;
return;
}
}
}
table被设计为下标为0,2,4…2n的位置存放key(==key的类型可以为null、tombstone、reference==),而1,3,5…(2n +1 )的位置存放value。直接通过下标存取线程变量,它比用WeakReference类在内存占用上更经济,性能也更好。这也是前面中hash的增量要取0x61c88647*2的原因,它也保证了其二进制中最低位为0,也就是在计算key的下标时,一定是偶数位。
存储数据到Values
void put(ThreadLocal<?> key, Object value) {
cleanUp();
int firstTombstone = -
1;
for (
int index = key.hash & mask;;
index = next(
index)) {
Object k = table[
index];
if (k == key.reference) {
table[
index +
1] = value;
return;
}
if (k ==
null) {
if (firstTombstone == -
1) {
table[
index] = key.reference;
table[
index +
1] = value;
size++;
return;
}
table[firstTombstone] = key.reference;
table[firstTombstone +
1] = value;
tombstones--;
size++;
return;
}
if (firstTombstone == -
1 && k == TOMBSTONE) {
firstTombstone =
index;
}
}
}
Values类存储key value过程会调用cleanup方法原因在于key为弱引用持有,当进行GC会对弱引用进行回收。处理完GC引起的数据异常后会依次遍历table的index,并在合适的位置进行存储key(ThreadLocal的弱引用)与线程对应变量value到table
cleanUp方法源码
private void cleanUp() {
if (rehash()) {
return;
}
if (size ==
0) {
return;
}
int index = clean;
Object[] table =
this.table;
for (
int counter = table.length; counter >
0; counter >>=
1,
index = next(
index)) {
Object k = table[
index];
if (k == TOMBSTONE || k ==
null) {
continue;
}
@SuppressWarnings(
"unchecked")
Reference<ThreadLocal<?>> reference
= (Reference<ThreadLocal<?>>) k;
if (reference.get() ==
null) {
table[
index] = TOMBSTONE;
table[
index +
1] =
null;
tombstones++;
size--;
}
}
clean =
index;
}
单独简单聊下0x61c88647
public class TestHash {
private static final
int HASH_INCREMENT =
0x61c88647;
public static void main(String args[]) {
magic_hash(
32);
}
public static void magic_hash(
int n) {
for (
int i =
0; i < n; i++) {
int nextHash = i * HASH_INCREMENT + HASH_INCREMENT;
System.
out.print((nextHash & (n -
1)) +
" ");
}
}
}
0x61c88647是实现散列常用的值,也是ThreadLocal的变量,用这个计算出来的散列值非常均匀
有可能的内存泄漏
1、ThreadLocal的实例被软引用持有,ThreadLocal使用完置为null没有强引用引用会被GC回收
2、value被当前线程强引用,如果为普通线程,那么任务执行完线程结束 Current Thread, Map, value将全部被GC回收;如果为线程池分配,任务执行完线程会重复使用,此时会导致内存泄漏
总之,每个Thread维护自己的Values类型的局部变量,而Values通过在数组table偶数位存储ThreadLocal为key.reference和奇数位具体变量value的方式完成通过ThreadLocal存取线程变量的过程