ThreadLocal提供一个(只有一个)线程的局部变量,为了确保多线程环境下,线程的安全性。其实可以这样理解,ThreadLocal其实就是一个普通类,它声明的对象有明确的作用范围,这个范围就是用ThreadLocal去声明对象的线程,当然每个线程可以有多个ThreadLocal变量,这些ThreadLocal变量被保存在ThreadLocalMap中,这个ThreadLocalMap其实相当于一个key-value结构的Map,结构大概是这样ThreadLocalMap
/**
* @author: ggp
* @Date: 2019/7/2 20:56
* @Description:
*/
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal t = new ThreadLocal<>();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i =0; i<100;i++){
if(null == t.get()){
t.set(new Number());
}
t.get().setVal(i);
System.out.println(Thread.currentThread().getName()+">>>>>>>>>>"+t.get().getVal());
}
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1000;i<1100;i++){
if(null == t.get()){
t.set(new Number());
}
t.get().setVal(i);
System.out.println(Thread.currentThread().getName()+">>>>>>>>>>"+t.get().getVal());
}
}
});
thread2.start();
}
}
class Number{
int val = 2;
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
}
可以看到两个线程互不干扰,确保了多线程之间的线程安全。
/**
* 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 the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
首先先获取到当前线程t,然后拿到线程的局部变量表ThreadLocalMap
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
跟踪源码可以看出ThreadLocalMap是 ThreadLocal的一个静态内部类,而Entry是ThreadLocalMap的基本组成单位
static class ThreadLocalMap {
/**
* 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> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
可以看到作者的注释,这个entry实现了弱引用的接口,而这个弱引用指向hashmap中的key值,所以当key值为null时,GC的时候就会回收key值,但是value并不是弱引用,所以不会被回收。entry没法被调用却也占着内存,就会造成内存泄露,直到当前线程结束,线程被回收,内存才会被完全释放。但是如果这个场景发生在线程池,那么内存泄露就会一直存在,直到应用结束。
但是,人家jdk早就有了解决方法。。。。。。。。。。
在entry的get和set方法中会做一个判断来清楚这些隐患
private Entry getEntry(ThreadLocal> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
以get为例,计算hash值,当拿到的entry中的key为空的时候进入 getEntryAfterMiss()中的expungeStaleEntry()方法中,通过源码可以看出,首先是将value置空,然后把整个entry置空,最后重新hash。