虽然在不同线程中访问的是同一个ThreadLocal对象,但是他们通过ThreadLocal获取到的值确实不一样的。因为不同线程访问同一个ThreadLocal的get()方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值,所以ThreadLocal可以在不同线程中维护一套数据副本且互不干扰。
ThreadLocal.set()
/**
-
Sets the current thread’s copy of this thread-local variable
-
to the specified value. Most subclasses will have no need to
-
override this method, relying solely on the {@link #initialValue}
-
method to set the values of thread-locals.
-
@param value the value to be stored in the current thread’s copy of
-
this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
先拿到当前的线程,然后拿到当前线程的一个map,如果存在这个map,直接往map里设值,如果这个map不存在,那先创建一个map,再往里面设值,ThreadLocalMap当于使用一个数组维护一张哈希表,负载因子是最大容量的2/3。
/**
- Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
下面我们进入ThreadLocalMap这个内部类里,下面的set方法实现了具体的Key和value存储,具体看看这个方法是怎么样的。
根据 key 找出 Entry 对象,如果找出的这个 Entry 的 k 等于 key,直接设置 Entry 的 value,如果 k 为空,则通过 replaceStaleEntry方法 保存数据,最后构建出 Entry 保存进 table 数组中。
/**
-
Set the value associated with key.
-
@param key the thread local object
-
@param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don’t use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在这个方法中,有一个变量叫做table,ThreadLocal的值就存在这个table中。Entry是Map中用来保存一个键值对的,而Map实际上就是多个Entry的集合,Entry<key,value>和Map<key,value>一样的理解方式。
在Android M(Android 6.0)之前,数据结构是通过Values实现的,Values中也有一个table的成员变量,table是一个Object数组,也是以类似map的方式来存储的。偶数单元存储的是key,key的下一个单元存储的是对应的value,所以每存储一个元素,需要两个单元,所以容量一定是2的倍数。
/**
-
The table, resized as necessary.
-
table.length MUST always be a power of two.
*/
private Entry[] table;
此哈希映射中的条目使用它的主ref字段作为键(它总是线程本地对象)。注意空键(即entry.get() == null)表示不再引用密钥,因此条目可以从表中删除。
Entry 继承了 WeakReference,那么通过 Entry 对象的 get 方法就可以获取到一个弱引用的 ThreadLocal 对象,Entry 保存了 ThreadLocal(key) 和 对应的值(value),其中 ThreadLoacl 是通过弱引用的形式,避免了线程池线程复用带来的内存泄露。
/**
-
The entries in this hash map extend WeakReference, using
-
its main ref field as the key (which is always a
-
ThreadLocal object). Note that null keys (i.e. entry.get()
-
== null) mean that the key is no longer referenced, so the
-
entry can be expunged from table. Such entries are referred to
-
as “stale entries” in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal.get()
/**
-
Returns the value in the current thread’s copy of this
-
thread-local variable. If the variable has no value for the
-
current thread, it is first initialized to the value returned
-
by an invocation of the {@link #initialValue} method.
- @return the current thread’s value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(“unchecked”)
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get()方法中,首先获取了当前的线程,然后又根据当前的线程取出map。
/**
-
Get the map associated with a ThreadLocal. Overridden in
-
InheritableThreadLocal.
-
@param t the current thread
-
@return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
这个getMap就是拿到了当前线程的ThreadLocalMap,继续回到get()方法里。
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(“unchecked”)
T result = (T)e.value;
return result;
}
}
如果获取的ThreadLocalMap这个map不为空,则以ThreadLocal的引用作为Key,在map中获取对应的Entry对象;如果获取的Entry对象也不为空的话,把它的value值返回出来。
在该方法的最后一句,也就是说当map为空的时候,则直接返回这个方法的结果。
return setInitialValue();
这个setInitialValue()方法是做什么的呢?
/**
-
Variant of set() to establish initialValue. Used instead
-
of set() in case user has overridden the set() method.
- @return the initial value
*/
private T setInitialValue() {
最后
考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档
喜欢的朋友可以关注、转发、点赞 感谢!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
T)e.value;
return result;
}
}
如果获取的ThreadLocalMap这个map不为空,则以ThreadLocal的引用作为Key,在map中获取对应的Entry对象;如果获取的Entry对象也不为空的话,把它的value值返回出来。
在该方法的最后一句,也就是说当map为空的时候,则直接返回这个方法的结果。
return setInitialValue();
这个setInitialValue()方法是做什么的呢?
/**
-
Variant of set() to establish initialValue. Used instead
-
of set() in case user has overridden the set() method.
- @return the initial value
*/
private T setInitialValue() {
最后
考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档
[外链图片转存中…(img-8C5x17Uf-1715626473674)]
[外链图片转存中…(img-z6cITaZq-1715626473676)]
喜欢的朋友可以关注、转发、点赞 感谢!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!