前言
ThreadLocal提供一個(只有一個)線程的局部變量,爲了確保多線程環境下,線程的安全性。其實可以這樣理解,ThreadLocal其實就是一個普通類,它聲明的對象有明確的作用範圍,這個範圍就是用ThreadLocal去聲明對象的線程,當然每個線程可以有多個ThreadLocal變量,這些ThreadLocal變量被保存在ThreadLocalMap中,這個ThreadLocalMap其實相當於一個key-value結構的Map,結構大概是這樣ThreadLocalMap<ThreadLocal,Object>。
例子
/**
* @author: ggp
* @Date: 2019/7/2 20:56
* @Description:
*/
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<Number> 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;
}
}
運行結果
可以看到兩個線程互不干擾,確保了多線程之間的線程安全。
源碼分析
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 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<ThreadLocal<?>> {
/** 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。