ThreadLocal一般稱為執行緒本地變數,它是一種特殊的執行緒繫結機制,將變數與執行緒繫結在一起,為每一個執行緒維護一個獨立的變數副本。通過ThreadLocal可以將物件的可見範圍限制在同一個執行緒內。
Demo
public class Demo {
private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public int getNext(){
Integer integer = threadLocal.get();
integer++;
threadLocal.set(integer);
return integer;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(() -> {
while (true){
System.out.println(Thread.currentThread().getName()+" :"+demo.getNext());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
while (true){
System.out.println(Thread.currentThread().getName()+" :"+demo.getNext());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
複製程式碼
Thread-0 :1
Thread-1 :1
Thread-1 :2
Thread-1 :3
Thread-1 :4
Thread-1 :5
Thread-1 :6
Thread-1 :7
Thread-1 :8
Thread-1 :9
Thread-1 :10
Thread-0 :2
......
複製程式碼
我們可以看到,執行緒0和執行緒1是相互不干擾的
原始碼分析
demo
中,我們用到ThreadLocal
的get()
方法和set()
方法,我們看看它底層是怎麼實現的,當然,還有個remove()
方法。這三個是最核心的API。
1. get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果map不為空,則拿出他的Entry,進而拿出entry的value
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
複製程式碼
return setInitialValue();
中的setInitialValue()
:
1.1 setInitialValue()
private T setInitialValue() {
//呼叫下面的initialValue方法
T value = initialValue();
//獲取當前執行緒
Thread t = Thread.currentThread();
//通過當前執行緒來獲取map
ThreadLocalMap map = getMap(t);
//如果map不為空,則set,如果為空,new一個ThreadLocalMap(createMap方法)
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
複製程式碼
1.2 createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
複製程式碼
1.3 getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;
複製程式碼
1.4 Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
複製程式碼
1.5 ThreadLocal和ThreadLocalMap對應關係
2. set(T value)
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
複製程式碼
set()
方法和上文的setInitialValue()
方法很相似。不過一個是面向業務程式設計,一個是物件初始化(無參)。
2.1 remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
複製程式碼
m.remove(this) 的 remove() 方法
2.2 ThreadLocalMap.remove
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
//獲取所有的Entry
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//遍歷查詢 key,如果存在,clear對應的Entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
複製程式碼
3. 總結
我們可以看到,ThreadLocal
和Thread
物件進行繫結,然後將Thread
做key
(實際上key並不是ThreadLocal本身,而是它的一個弱引用),Object
做value
存放在map
中。也就是說每個執行緒有一個自己的ThreadLocalMap。呼叫get
方法時,獲取到當前物件然後獲取value
進而進行操作,在往某個ThreadLocal
裡塞值的時候,都會往自己的ThreadLocalMap
裡存,讀也是以某個ThreadLocal
作為引用,在自己的map
裡找對應的key
,從而實現了執行緒隔離。