ThreadLocal是一個非常常用對象,線程的變量副本,每個線程隔離,但對具體如何實現線程隔離,這篇文章試着去了解一下。
hreadLocal代碼演示
public class ThreadLocalDemo {
private static ExecutorService executor = new ThreadPoolExecutor(5, 5,
10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
int j = i;
executor.execute(() -> {
String value = "" + j + j + j;
threadLocal.set(value);
System.out.println(Thread.currentThread().getName() + "設置值:" + value);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
String getValue = threadLocal.get();
System.out.println(Thread.currentThread().getName() + "獲取:" + getValue);
});
}
}
}
不同線程設置的值和獲取的值是同一個,實現線程之間設置的隔離。
ThreadLocal的數據結構
對於一個ThreadLocal 而言
Thread類有一個類型爲ThreadLocal.ThreadLocalMap的成員變量inheritableThreadLocals
如果是多個ThreadLocal的展示情況:
public class ThreadLocalDemo {
public static void main(String[] args) throws Exception {
ThreadLocal<String> local1 = new ThreadLocal<>();
ThreadLocal<String> local2 = new ThreadLocal<>();
ThreadLocal<String> local3 = new ThreadLocal<>();
ThreadLocal<String> local4 = new ThreadLocal<>();
local1.set("local1");
local2.set("local2");
local3.set("local3");
local4.set("local4");
//得到當前線程
Thread t = Thread.currentThread();
Class<? extends Thread> clz = t.getClass();
//獲取當前線程threadLocals class也就是 ThreadLocal.ThreadLocalMap
Field field = clz.getDeclaredField("threadLocals");
field.setAccessible(true);
//得到 ThreadLocal.ThreadLocalMap具體實例
Object threadLocalMap = field.get(t);
Class<?> tlmClass = threadLocalMap.getClass();
//獲取到 ThreadLocal.ThreadLocalMap 中的數組 class
Field tableField = tlmClass.getDeclaredField("table");
tableField.setAccessible(true);
//獲取到 ThreadLocal.ThreadLocalMap 中的數組 中實例對象
Object[] arr = (Object[]) tableField.get(threadLocalMap);
for (Object o : arr) {
if (o != null) {
Class<?> entryClass = o.getClass();
Field valueField = entryClass.getDeclaredField("value");
// 繼承關係 由static class Entry extends WeakReference<ThreadLocal<?>>
// 可得 entryClass.getSuperclass() 獲取WeakReference 的class
//繼承關係 public class WeakReference<T> extends Reference<T>
//可得 entryClass.getSuperclass().getSuperclass() 得到 Reference的繼承關係
//referent 是 Reference 的成員變量,也是threadLocal
Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
valueField.setAccessible(true);
referenceField.setAccessible(true);
System.out.println(String.format("key:%s,值:%s", referenceField.get(o), valueField.get(o)));
}
}
}
}
從圖片和代碼中可以同一個線程的不同ThreadLocal和值放到同一個線程中成員變量threadLocals中
上面的例子雖然定義ThreadLocal,可能會讓人聯想到數據都是存在ThreadLocal這個類中,
但實際上Thread類有一個類型爲ThreadLocal.ThreadLocalMap的成員變量threadLocals,也就是說每個線程有一個自己的ThreadLocalMap。
ThreadLocal.ThreadLocalMap 將threadLocal 作爲key進行存儲,value爲我們設置的值。對於同一個ThreadLocal來說,不同線程將值存儲到不同線程內部的實例變量中,就對於單個threadLocal 就不存在線程安全的問題。
ThreadLocal的set 方法
// threadLocal 的get方法
public void set(T value) {
Thread t = Thread.currentThread();
//ThreadLocalMap 是取至thread的成員變量
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//從之前圖片上Thread 中threadLocals的成員變量,和這可以看出中ThreadLocalMap 是懶加載
createMap(t, value);
}
// threadLocal 中的getMap方法可以看出,ThreadLocalMap 是取至thread的成員變量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//這個是ThreadLocalMap 中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;
//算出ThreadLocalMap 中數組的下標
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) {
//清空槽位上key爲null的值
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//調用cleanSomeSlots()做一次啓發式清理工作,清理散列數組中Entry的key過期的數據,
//如果無數據清理,且數據量超過超過了閾值(數組長度的2/3),進行rehash()操作
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//擴容
rehash();
}
// nextIndex一直向後,直到entry不爲空
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
//
private void rehash() {
//清理數據
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
//清理完如果容量超過3/4則擴容
if (size >= threshold - threshold / 4)
resize();
}
//從傳入節點清理數據,如果有數據清理則爲true ,無數據清理則爲false
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
ThreadLocal的get方法
public T get() {
Thread t = Thread.currentThread();
//和set方法一樣,去Thread 獲取 thread的成員變量
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();
}
//通過threadLocal 獲取節點
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果直接是該threadLocal 直接返回
if (e != null && e.get() == key)
return e;
else
//1、該節點上面值爲null 2、該節點和其他節點hash衝突了
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//e 如果直接爲null 不進入while循環,直接返回 null
while (e != null) {
ThreadLocal<?> k = e.get();
//遇到該hash的值,直接返回
if (k == key)
return e;
//如果過期,清理過期值
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
ThreadLocal中ThreadLocalMap 的 Entry extends WeakReference
//ThreadLocalMap 的 Entry extends WeakReference<ThreadLocal<?>> 中key的值是弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
但value是set的值屬於強引用,當threadlocal 爲gc回收之後,value一直留在ThreadLocalMap 之中。造成內存泄漏,從源碼上分析,get,set,擴容 的時候都有檢查是否ThreadLocalMap 中是否key過期,如果過期將value設置爲空。我們在使用當中,如果該value中值不再使用可以使用remove方法置value置空。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}