Android中的ThreadLocal源码解析
程序员文章站
2022-06-07 22:16:23
...
我在之前的文章《Android中的Looper,Handler及HandlerThread简析》(http://maosidiaoxian.iteye.com/blog/1927735)中有提到过ThreadLocal,后来一直想详细读读这个类。前几天写完了Java原生的ThreadLocal,今天来看一下Android中的ThreadLocal类。在读这篇文章之前,建议先读一下我前面写的关于Java中的ThreadLocal解析的两篇文章,因为在这里对一些相同的内容我会不再赘述。
Android中的ThreadLocal的源码在libcore/luni/src/main/java/java/lang目录下,如果你没有下载Android源码,可通过以下地址读到该类的源码:https://android.googlesource.com/platform/libcore/+/android-4.3_r2.2/luni/src/main/java/java/lang/ThreadLocal.java
在Android中,ThreadLocal像是对原来的ThreadLcal做了优化的实现。我们同样先看看这个类的结构,如下图:
可以直观地看到在android中ThreadLocal比java原生的这个类少了一些API,而且保存线程变量的内部类名字也改为Values,里面没有再定义内部类。仔细地阅读比较,我们可以看到Android中对Java原生的ThreadLocal做了一些优化的工作。
先来看看ThreadLocal的变量,代码如下:
在这里还多定义了一个变量,Reference<ThreadLocal<T>> reference,它是一个弱引用,引用ThreadLocal实例的自己。
而当实例化一个ThreadLocal对象时,仅仅是生成一个hash值,和对reference赋值。
在set方法中,依然是从线程中取得保存变量的对象,在这里是values,如果values为null就进行初始化(对thread对象创建Values对象并返回),然后调用其put方法保存变量,与Java原生的思路都是一样的,只是代码简化了许多,如下:
remove()方法就不谈了,很简单,跟原来的基本一致。
下面来看一下get()方法。
我们会注意到其中的代码:
为什么用hash取得下标后,下一位才是保存的变量值呢?
我们来看一下Values这个类是怎么定义和设计的。
Values是被设计用来保存线程的变量的一个类,它相当于一个容器,存储保存进来的变量。它的成员变量如下:
同样table是实际上保存变量的地方,但它在这里是个Object类型的数组,它的长度必须是2的n次方的值。mask即计算下标的掩码,它的值是table的长度-1。size表示存放进来的实体的数量。这与前面原生的ThreadLocal的ThreadLocalMap是一样的。但是在这里它还定义了三个int类型的变量:tombstones表示被删除的实体的数量,maximumLoad是一个阈值,用来判断是否需要进行rehash,clean表示下一个要进行清理的位置点。
我们来看一下当Values对象被创建时进行了什么工作,代码如下:
上面的代码我们可以看到,当初始化一个Values对象时,它会创建一个长度为capacity*2的数组。
然后在add()方法当中,也可以看到它会把ThreadLocal对象(key)和对应的value放在连续的位置中。
也就是table被设计为下标为0,2,4...2n的位置存放key,而1,3,5...(2n +1 )的位置存放value。直接通过下标存取线程变量,它比用WeakReference<ThreadLocal>类在内存占用上更经济,性能也更好。这也是前面中hash的增量要取0x61c88647*2的原因,它也保证了其二进制中最低位为0,也就是在计算key的下标时,一定是偶数位。
而在remove()方法中,移除变量时它是把对应的key的位置赋值为TOMBSTONE,value赋值为null,然后 tombstones++;size--;。TOMBSTONE是前面定义的一个常量,表示被删除的实体。
其他方法的算法,其实与Java原生的一样,只是做了对应于Values类的设计的修改。这里不再赘述。
Android中的ThreadLocal的源码在libcore/luni/src/main/java/java/lang目录下,如果你没有下载Android源码,可通过以下地址读到该类的源码:https://android.googlesource.com/platform/libcore/+/android-4.3_r2.2/luni/src/main/java/java/lang/ThreadLocal.java
在Android中,ThreadLocal像是对原来的ThreadLcal做了优化的实现。我们同样先看看这个类的结构,如下图:
可以直观地看到在android中ThreadLocal比java原生的这个类少了一些API,而且保存线程变量的内部类名字也改为Values,里面没有再定义内部类。仔细地阅读比较,我们可以看到Android中对Java原生的ThreadLocal做了一些优化的工作。
先来看看ThreadLocal的变量,代码如下:
/** Weak reference to this thread local instance. */ private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this); /** Hash counter. */ private static AtomicInteger hashCounter = new AtomicInteger(0); /** * Internal hash. We deliberately don't bother with #hashCode(). * Hashes must be even. This ensures that the result of * (hash & (table.length - 1)) points to a key and not a value. * * We increment by Doug Lea's Magic Number(TM) (*2 since keys are in * every other bucket) to help prevent clustering. */ private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);在这里,ThreadLocal没有再定义HASH_INCREMENT 这个常量,而是直接写在了hash变量的定义当中。将原来的nextHashCode的代码与hash的定义合在了一起,增量则是0x61c88647 * 2。
在这里还多定义了一个变量,Reference<ThreadLocal<T>> reference,它是一个弱引用,引用ThreadLocal实例的自己。
而当实例化一个ThreadLocal对象时,仅仅是生成一个hash值,和对reference赋值。
在set方法中,依然是从线程中取得保存变量的对象,在这里是values,如果values为null就进行初始化(对thread对象创建Values对象并返回),然后调用其put方法保存变量,与Java原生的思路都是一样的,只是代码简化了许多,如下:
public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); } /** * Gets Values instance for this thread and variable type. */ Values values(Thread current) { return current.localValues; } /** * Creates Values instance for this thread and variable type. */ Values initializeValues(Thread current) { return current.localValues = new Values(); }
remove()方法就不谈了,很简单,跟原来的基本一致。
下面来看一下get()方法。
/** * Returns the value of this variable for the current thread. If an entry * doesn't yet exist for this variable on this thread, this method will * create an entry, populating the value with the result of * {@link #initialValue()}. * * @return the current value of the variable for the calling thread. */ @SuppressWarnings("unchecked") 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); }
我们会注意到其中的代码:
if (this.reference == table[index]) { return (T) table[index + 1]; }
为什么用hash取得下标后,下一位才是保存的变量值呢?
我们来看一下Values这个类是怎么定义和设计的。
Values是被设计用来保存线程的变量的一个类,它相当于一个容器,存储保存进来的变量。它的成员变量如下:
/** * 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;
同样table是实际上保存变量的地方,但它在这里是个Object类型的数组,它的长度必须是2的n次方的值。mask即计算下标的掩码,它的值是table的长度-1。size表示存放进来的实体的数量。这与前面原生的ThreadLocal的ThreadLocalMap是一样的。但是在这里它还定义了三个int类型的变量:tombstones表示被删除的实体的数量,maximumLoad是一个阈值,用来判断是否需要进行rehash,clean表示下一个要进行清理的位置点。
我们来看一下当Values对象被创建时进行了什么工作,代码如下:
/** * Constructs a new, empty instance. */ Values() { initializeTable(INITIAL_SIZE); this.size = 0; this.tombstones = 0; } /** * Creates a new, empty table with the given capacity. */ 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 }
上面的代码我们可以看到,当初始化一个Values对象时,它会创建一个长度为capacity*2的数组。
然后在add()方法当中,也可以看到它会把ThreadLocal对象(key)和对应的value放在连续的位置中。
/** * 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,而1,3,5...(2n +1 )的位置存放value。直接通过下标存取线程变量,它比用WeakReference<ThreadLocal>类在内存占用上更经济,性能也更好。这也是前面中hash的增量要取0x61c88647*2的原因,它也保证了其二进制中最低位为0,也就是在计算key的下标时,一定是偶数位。
而在remove()方法中,移除变量时它是把对应的key的位置赋值为TOMBSTONE,value赋值为null,然后 tombstones++;size--;。TOMBSTONE是前面定义的一个常量,表示被删除的实体。
其他方法的算法,其实与Java原生的一样,只是做了对应于Values类的设计的修改。这里不再赘述。