什麼是ThreadLocal
Java doc的解釋
ThreadLocal提供了線程局部可用的變量. 這些變量在每個線程中都維護有自己的獨立副本. ThreadLocal類型的變量一般是一個類的靜態變量來存儲與線程相關的變量 (比如用戶ID, 事務ID).
每個活的線程都持有thread-local變量的精確引用. 當線程terminated後, 該變量變得可以被GC(在沒有其他引用後).
ThreadLocal可以做什麼?
常見用途.
存儲一些本身不是線程安全的類
比如SimpleDateFormat不是線程安全的, 我們可以用ThreadLocal來存儲
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadLocalDemo {
static ThreadLocal<SimpleDateFormat> _formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static String formate(Date date) {
return _formatter.get().format(date);
}
public static void main(String[] args) {
System.out.println(formate(new Date()));
}
記錄一些上下文相關的信息
比如前面寫的userid, transactionid
我們用ThreadLocal來實現打印日誌中包含正確的Component信息.
import java.util.Date;
public class ThreadLocalLooggerDemo {
public static void main(String[] args) throws InterruptedException {
Thread task = new Thread(new Runnable() {
@Override
public void run() {
Log.setTaskId("Consumer");
Log.info("I started");
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
Log.info("I finished");
}
});
task.start();
task.join();
}
static class Log {
static ThreadLocal<String> taskId = ThreadLocal.withInitial(() -> "N/A");
public static void setTaskId(String taskIdStr) {
taskId.set(taskIdStr);
}
public static String getTaskId() {
return taskId.get();
}
public static void info(String message) {
System.out.println(String.format("[%s][%s]%s", new Date(), taskId.get(), message));
}
}
}
示例輸出
[Wed Jul 10 13:59:10 CST 2019][Consumer]I started
[Wed Jul 10 13:59:11 CST 2019][Consumer]I finished
實現細節
set 方法
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
- 每個Thread有一個自己的變量map:
ThreadLocal.ThreadLocalMap threadLocals = null
- 該map是一個ThreadLocal內部定義的map. map裏面的每個Entry都是
WeakReference<ThreadLocal>
. - 使用WeakReference是因爲 我們不能一直持有一個Thread或者ThreadLocal的引用這會導致它沒法被GC, 這就有了內存泄漏. 而WeakReference沒有這個問題. 這就是後面如果Entry的key爲NULL時, 它的值也被擦除了. (它的值就是這裏的SimpleDateFormat)
int i = key.threadLocalHashCode & (len-1); // 根據key (threadlocal對象)找到tab[]的index
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) { // 循環遍歷 tab, nextIndex 就是從i開始自增 並取模len
ThreadLocal<?> k = e.get();
if (k == key) { // 如果是同一個對象 指針比較
e.value = value; // 更新值返回
return;
}
if (k == null) { // 當前位置沒人用 就放在當前位置
replaceStaleEntry(key, value, i); // 該方法還會做一些動作 就是
return;
}
}
get方法
Thread t = Thread.currentThread(); // 一直使用當前Thread的threadlocalmap
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(); // 沒有返回初始值 如果有設置的話
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e; // 直接根據hash值找
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); // 擦除掉當前entry key爲null的值, key和value都爲NULL
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
// 擦除key爲NULL的方法
private int expungeStaleEntry(int staleSlot) { // 當前key爲null的slot
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot // 擦除當前slot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
// for 循環
for (i = nextIndex(staleSlot, len); // 向下檢查下一個slot
(e = tab[i]) != null; // Entry有東西
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) { // 擦除key 爲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;
}