入坑JAVA多執行緒併發(八)詳解ThreadLocal使用和原理
ThreadLocal是一個用於儲存多執行緒變數的類,它可以把執行緒與設定的值對應起來,因為它為變數在每個執行緒都建立了一個副本。訪問的時候每個執行緒只能訪問到自己的副本變數。
例項
看如下程式碼:
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = new ThreadLocal();
Thread1 thread1 = new Thread1("執行緒A" ,threadLocal);
Thread1 thread2 = new Thread1("執行緒B",threadLocal);
Thread1 thread3 = new Thread1("執行緒C",threadLocal);
Thread1 thread4 = new Thread1("執行緒D",threadLocal);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class Thread1 extends Thread{
ThreadLocal<String> threadLocal;
public Thread1(String name,ThreadLocal<String> threadLocal){
super(name);
this.threadLocal = threadLocal;
}
@Override
public void run() {
try {
for(int i = 0;i< 10;i++){
threadLocal.set(getName()+i);
System.out.println(getName()+": " +threadLocal.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出為:
執行緒B: 執行緒B0
執行緒D: 執行緒D0
執行緒A: 執行緒A0
執行緒C: 執行緒C0
執行緒C: 執行緒C1
執行緒A: 執行緒A1
執行緒B: 執行緒B1
執行緒D: 執行緒D1
執行緒D: 執行緒D2
執行緒C: 執行緒C2
執行緒A: 執行緒A2
執行緒B: 執行緒B2
執行緒B: 執行緒B3
執行緒D: 執行緒D3
執行緒A: 執行緒A3
執行緒C: 執行緒C3
執行緒C: 執行緒C4
執行緒A: 執行緒A4
執行緒B: 執行緒B4
執行緒D: 執行緒D4
可以看到效果和預期是一樣的,每個執行緒都只是獲取自己設定的值。
原理
每個執行緒內部儲存著一個ThreadLocalMap 例項的,ThreadLocalMap 例項儲存著一個或者多個ThreadLocal例項,獲取的時候就可以根據當前執行緒獲得對應設定的值
ThreadLocal的構造方法只有一個預設的無參構造方法;
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);
}
//獲取Map進行設值,如果沒有就新建一個map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //Thread的一個屬性,初始值為NUll;
}
//這個是Thread的程式碼,每個Thread儲存一個map的
ThreadLocal.ThreadLocalMap threadLocals = null;
//新建一個Map,ThreadLocalMap是內部類
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); //設定值
}
//ThreadLocalMap的構造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
可以看出這裡是把每個執行緒的threadLocals 設定為一個ThreadLocalMap 的例項,ThreadLocalMap 儲存的是key是threadLocals例項,value是對應的值
如下圖:
每個執行緒都有一個threadLocals ,指向對應的ThreadLocalMap 例項,ThreadLocalMap 是一個map結構,ThreadLocalMap 裡面每個Entry儲存著一個對應的value值,Entry例項的key是ThreadLocal例項,value是當前執行緒在這個ThreadLocal設定的value值;如果對map結果不熟,可參考Java從入門到放棄(十)集合框架之HashMap原始碼(1);
ThreadLocalMap的Hash,ThreadLocalMap的key值取的是ThreadLocal例項,這裡hash和hashmap有一些不同:
private final int threadLocalHashCode = nextHashCode(); //hash值
private static AtomicInteger nextHashCode =
new AtomicInteger(); //原子操作類
}
private static final int HASH_INCREMENT = 0x61c88647; //每次hash間隔
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT); //獲取下一個hash值
}
這裡是把hash值按照HASH_INCREMENT 的間隔相加。這樣子可以保證不會出現相同hash的情況,避免Entry陣列的Entry太過於集中,讓Entry均勻分佈。(這個數字可以讓計算出來的索引比較均勻分佈)
get方法
public T get() {
Thread t = Thread.currentThread(); //獲取當前執行緒
ThreadLocalMap map = getMap(t); //獲取map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //獲取對應Entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //沒有就返回初始化的值
}
//設定初始化的值
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;
}
//初始化
protected T initialValue() {
return null;
}
這裡就是獲取當前執行緒,取得執行緒的map,根據Threadlocal為key找到對應的value值。如果沒有設定就初始化並且設定對應值為null;
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
這個也很簡單,就是獲取對應的map然後刪除。