線程封閉概念
多線程訪問共享數(shù)據(jù)為了安全性通常需要同步,如果僅在單線程內(nèi)訪問數(shù)據(jù)就不需要同步法严,這種避免共享數(shù)據(jù)的技術(shù)稱為線程封閉嚼隘。
ThreadLocal
JDK提供ThreadLocal類防止線程內(nèi)的可變變量的共享,通過ThreadLocal類可以保證線程特有的變量封閉在線程內(nèi)而不會逸出到該線程外手负。
初始化ThreadLocal
- 方式一,重寫父類函數(shù)
ThreadLocal<Object> localVariable = new ThreadLocal<Object>(){
protected Object initialValue() {
return "abcd2";
};
};
這種方式下姑尺,給ThreadLocal添加了一個默認值竟终,在ThreadLocal調(diào)用get方法取值時,如果并未調(diào)用set方法設置新值時會調(diào)用setInitialValue()方法:
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();
}
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;
}
可以看到setInitialValue內(nèi)部將this指針指向的當前ThreadLocal作為鍵,value作為值切蟋,插入到了ThreadLocalMap中统捶。
- 方式二,調(diào)用set方法
ThreadLocal<Object> trLocal = new ThreadLocal<>();
localVariable.set("abcd");
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
這種方式與在ThreadLocal重寫initialValue方法的區(qū)別是,不會將值的設置推遲到get方法的執(zhí)行喘鸟。
ThreadLocalMap 的創(chuàng)建
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在Thread類內(nèi)部有一個ThreadLocal.ThreadLocalMap的threadLocals變量匆绣,該變量在createMap方法的調(diào)用下被賦值一個新的ThreadLocalMap的實例。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private static final int INITIAL_CAPACITY = 16;
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);
}
}
ThreadLocalMap相當于線程本地變量的映射表什黑。內(nèi)部由Entry[]數(shù)組實現(xiàn)鍵值對的保存崎淳,其中鍵為ThreadLocal,值為線程本地變量愕把。該數(shù)組初始值INITIAL_CAPACITY是16拣凹。
ThreadLocalMap 加入鍵值對
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); @1
for (Entry e = tab[i];
e != null;@2
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { @3
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); @2
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
@1 先通過ThreadLocal計算出hashCode,該hashCode在ThreadLocal由靜態(tài)共享的AtomicInteger每次自增HASH_INCREMENT(0x61c88647)實現(xiàn)恨豁。根據(jù)hashCode算出鍵對應Entry[]數(shù)組的位置i嚣镜。
@2 Entry[i]為空,將ThreadLocal和value對象封裝成一個Entry對象插入到數(shù)組的i位置上圣絮。如果插入后未清除無效元素后祈惶,并且已達到數(shù)組的邊界,接下來會對數(shù)組擴容兩倍(JDK 1.8)
@3 如果Entry[i]非空扮匠,如果Entry[i]上的鍵與加入的鍵相等,重新設置值的引用為新加入的值凡涩。
@4 如果Entry[i]上的鍵為空棒搜,說明該鍵值對已失效,調(diào)用replaceStaleEntry刪除table中所有失效元素并將新的鍵值對插入數(shù)組活箕。
@5 算出下一個數(shù)組中的位置力麸,重復@2、@3育韩、@4的步驟克蚂。
ThreadLocal 取值
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();
}
先判斷當前線程是否有ThreadLocalMap,若不存在調(diào)用setInitialValue方法返回initialValue方法返回的默認值筋讨。
若當前線程的ThreadLocalMap非空埃叭,調(diào)用ThreadLocalMap的getEntry方法取出Entry:
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
該方法先計算出ThreadLocal的哈希值,再由該哈希值按位與數(shù)組的長度計算出Entry的位置i悉罕。
如果Entry[i]非空并且它內(nèi)部的弱引用ThreadLocal非空赤屋,將Entry返回并取它的value。否則調(diào)用getEntryAfterMiss方法繼續(xù)從數(shù)組的下個位置尋找Entry壁袄。
原理小結(jié)
使用ThreadLocal將線程本地變量封裝到了線程的ThreadLocalMap中类早,想要從ThreadLocal取值就必須經(jīng)過線程本地的ThreadLocalMap,因此本線程外訪問ThreadLocalMap是無效的,避免了對線程本地變量的干擾嗜逻。