應用案例——多連結串列共享同一套連結串列操作
1.定義
執行緒本地的變數副本,屬於每個執行緒獨有。每個執行緒使用ThreadLocal設定自己的值,設定的值之間不受影響,但是使用同一個ThreadLocal物件。所以設定的每個變數,是給每個執行緒一個獨有的變數副本
2.簡單使用
public class HelloThreadLocal { private static ThreadLocal<Loan> threadLocal = new ThreadLocal<Loan>(); public static void main(String[] args) { //執行緒1 使用threadLocal設定自己的變數副本 new Thread(() -> { threadLocal.set(new Loan("zhangsan", "1000.00")); System.out.println("執行緒-1loan:" + threadLocal.get()); }).start(); //執行緒2 使用threadLocal設定自己的變數副本 new Thread(() -> { try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { } HelloThreadLocal.Loan loan = threadLocal.get(); System.out.println("執行緒-2loan:" + loan); threadLocal.set(new Loan("lisi", "2000.00")); loan = threadLocal.get(); System.out.println("執行緒-2loan:" + loan); }).start(); try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { } System.out.println("main-執行緒loan:" + threadLocal.get()); threadLocal.set(new Loan("wangwu", "1000.00")); System.out.println("main-執行緒loan:" + threadLocal.get()); } @Data @AllArgsConstructor public static class Loan { private String name; private String amount; } }
輸出結果:
執行緒-1loan:HelloThreadLocal.Loan(name=zhangsan, amount=1000.00)
執行緒-2loan:null
執行緒-2loan:HelloThreadLocal.Loan(name=lisi, amount=2000.00)
main-執行緒loan:null
main-執行緒loan:HelloThreadLocal.Loan(name=wangwu, amount=1000.00)
可以看出,每個執行緒無法獲取到其它執行緒設定的loan物件
3.原始碼分析
1.get方法
首先獲取當前執行緒的ThreadLocalMap變數,如果map為空的話呼叫setInitialValue方法返回預設值,如果map不為空獲取entry中key對應的value值
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(); }
getMap方法:
threadLocals變數用於儲存當前執行緒自身的ThreadLocal,所以雖然使用的是ThreadLocal的get方法,但是操作的實際是當前執行緒的threadLocals本地遍歷副本的Map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
setInitialValue方法:
首先初始化value為null,然後當前執行緒獲取map,如果為null的話,建立map,否則直接set,這裡會返回null,所以get方法也會返回null的值
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
2.set方法
如果是第一次使用set方法,建立一個預設大小為16的ThreadLocalMap,並將key設為ThreadLocalMap物件,value用傳入的value,如果不是第一次直接map.set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
3.ThreadLocalMap
ThreadLocalMap中的核心結構是一個Entry,用來儲存key-value資料:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
這裡繼承了弱引用,弱引用是隻要gc發現了它,就會回收掉,這裡的key使用弱引用是為了防止記憶體洩漏,因為如果key是強引用的話,當一個threadLocal物件為null後,還是會指向它,導致該物件不能被回收,造成記憶體洩漏,這裡雖然key使用了弱引用,但是還是存在value指向的強引用,所以需要用remove方法來刪除之前的key,不然還是會造成記憶體洩漏
4.應用場景
1.Spring的Transaction機制中,將一個執行緒中的事務放入ThreadLocal中,可以在整個方法呼叫棧中隨時取出事務的資訊進行操作,不會影響其它執行緒
2.Log4j2等日誌框架中的MDC
3.HDFS edits_log的txId自增後放入執行緒本地副本,HDFS的每條edit_log都有一個txId,會將這個txId記錄到當前執行緒方便在整個執行緒過程中隨時取用
小結:
ThreadLocal最常用的2個場景就是:
1.執行緒中,在各個方法需要共享變數時使用。除了方法之間傳遞入參,通過ThreadLocal可以很方便的做到這一點
2.多執行緒操作時,防止併發衝突,保證執行緒安全。比如一般會拷貝一份資料到執行緒本地,自己修改本地變數,是執行緒安全的