ThreadLocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值,ThreadLocal不是用来解决共享对象的多线程访问问题的,通过ThreadLocal的set()方法设置到线程的ThreadLocal.ThreadLocalMap里的是是线程自己要存储的对象,其他线程不需要去访问,也是访问不到的。各个线程中的ThreadLocal.ThreadLocalMap以及ThreadLocal.ThreadLocal中的值都是不同的对象。
1、每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象
2、每一个ThreadLocal对象都有一个循环计数器
3、ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据第二点中循环计数器取得一个特定value值
set
1、先对ThreadLocal里面的threadLocalHashCode取模获取到一个table中的位置
2、这个位置上如果有数据,获取这个位置上的ThreadLocal
(1)判断一下位置上的ThreadLocal和我本身这个ThreadLocal是不是一个ThreadLocal,是的话数据就覆盖,返回
(2)不是同一个ThreadLocal,再判断一下位置上的ThreadLocal是是不是空的,这个解释一下。Entry是ThreadLocal弱引用,"static class Entry extends WeakReference
(3)上面都没有返回,给模加1,看看模加1后的table位置上是不是空的,是空的再加1,判断位置上是不是空的...一直到找到一个table上的位置不是空的为止,往这里面塞一个value。换句话说,当table的位置上有数据的时候,ThreadLocal采取的是办法是找最近的一个空的位置设置数据。
3、如果没有数据,实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
/**
* Sets the current thread's copy of this thread-local variable 将此线程局部变量的当前线程副本设置为指定值。 大多数子类将不需要重写此方法,而仅依靠{@link #initialValue}方法来设置线程局部变量的值。
* 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 @param value要存储在此本地线程的当前线程副本中的值。
* 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);
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* 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;
//对ThreadLocal里面的threadLocalHashCode取模获取到一个table中的位置
int i = key.threadLocalHashCode & (len-1);
//这个位置上如果有数据,获取这个位置上的ThreadLocal
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
//判断一下位置上的ThreadLocal和我本身这个ThreadLocal是不是一个ThreadLocal,是的话数据就覆盖,返回
if (k == key) {
e.value = value;
return;
}
//不是同一个ThreadLocal,再判断一下位置上的ThreadLocal是是不是空的,Entry是ThreadLocal弱引用,"static class Entry extends WeakReference",有可能这个ThreadLocal被垃圾回收了
if (k == null) {
//设置新的value替换到当前位置上,返回
replaceStaleEntry(key, value, i);
return;
}
}
//上面都没有返回,给模加1,看看模加1后的table位置上是不是空的,是空的再加1,判断位置上是不是空的...一直到找到一个table上的位置不是空的为止,往这里面塞一个value。换句话说,当table的位置上有数据的时候,ThreadLocal采取的 //是办法是找最近的一个空的位置设置数据
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
get
1、获取当前线程
2、尝试去当前线程中拿它的ThreadLocal.ThreadLocalMap
3、当前线程中判断是否有ThreadLocal.ThreadLocalMap
(1)有就尝试根据当前ThreadLocal的threadLocalHashCode取模去table中取值,有就返回,没有就给模加1继续找,这和设置的算法是一样的
(2)没有就调用set方法给当前线程ThreadLocal.ThreadLocalMap设置一个初始值
/**
* Returns the value in the current thread's copy of this 返回此线程局部变量的当前线程副本中的值。 如果该变量没有当前线程的值,则首先将其初始化为调用{@link #initialValue}方法返回的值。
* 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();
//尝试去当前线程中拿它的ThreadLocal.ThreadLocalMap
ThreadLocalMap map = getMap(t);
//判断当前线程中判断是否有ThreadLocal.ThreadLocalMap
if (map != null) {
//有就尝试根据当前ThreadLocal的threadLocalHashCode取模去table中取值,有就返回,没有就给模加1继续找
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//没有就调用set方法给当前线程ThreadLocal.ThreadLocalMap设置一个初始值
return setInitialValue();
}
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal> key) {
//根据当前ThreadLocal的threadLocalHashCode取模去table中取值
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//有就返回,没有就给模加1继续找
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
remove
/**
* Removes the current thread's value for this thread-local 删除此线程局部变量的当前线程值。 如果此线程局部变量随后被当前线程{@linkplain #get read}调用,则其值将通过调用其{@link #initialValue}方法来重新初始化,除非
* variable. If this thread-local variable is subsequently 当前值是{@linkplain #set set} 在此期间穿线。 这可能会导致在当前线程中多次调用{@code initialValue}方法。
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
//取得当前线程的ThreadLocal.ThreadLocalMap,如果有ThreadLocal.ThreadLocalMap,找到对应的Entry,移除掉
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap
实例化ThreadLocalMap时创建了一个长度为16的Entry数组。通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置,每个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是通过操作这个数组table完成的。
/**
* Construct a new map initially containing (firstKey, firstValue). 构造一个最初包含(firstKey,firstValue)的Map。 ThreadLocalMaps是延迟构造的,因此只有在至少有一个条目要放置时才创建一个。
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
//长度为16的Entry数组
table = new Entry[INITIAL_CAPACITY];
//被存储在table数组中的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置长度
setThreshold(INITIAL_CAPACITY);
}
/**
* Set the resize threshold to maintain at worst a 2/3 load factor. 重新设置长度
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
结论
- ThreadLocal不需要key,因为线程里面自己的ThreadLocal.ThreadLocalMap不是通过链表法实现的,而是通过开地址法实现的
- 每次set的时候往线程里面的ThreadLocal.ThreadLocalMap中的table数组某一个位置塞一个值,这个位置由ThreadLocal中的threadLocaltHashCode取模得到,如果位置上有数据了,就往后找一个没有数据的位置
- 每次get的时候也一样,根据ThreadLocal中的threadLocalHashCode取模,取得线程中的ThreadLocal.ThreadLocalMap中的table的一个位置,看一下有没有数据,没有就往下一个位置找
- 既然ThreadLocal没有key,那么一个ThreadLocal只能塞一种特定数据。如果想要往线程里面的ThreadLocal.ThreadLocalMap里的table不同位置塞数据 ,比方说想塞三种String、一个Integer、两个Double、一个Date,请定义多个ThreadLocal,ThreadLocal支持泛型"public class ThreadLocal
"