面試官:「先問一個問題,如何在多線程的環境下保證數據不被其餘線程修改?」java
能夠把這個數據用 ThreadLocal
封裝一下面試
面試官:「噢,那你說一下 ThreadLocal 是什麼吧,它和用鎖有什麼不同?」編程
ThreadLocal
提供了線程隔離的變量,它通常以 private static
的形式出現,能夠視做類中的全局變量,只不過每一個線程擁有本身的變量數據。 至於它和鎖有什麼不同,我以爲這個問題就不太好,由於它們根本就不是一類東西。對於數據使用方面來講,鎖主要解決的是併發環境下數據競爭的問題,而 ThreadLocal
根本就不是解決併發問題的,由於自己就不存在併發的業務。簡單來講,ThreadLocal
就比如身份證,咱們能夠拿身份證去網吧,去坐高鐵,去住酒店,但咱們每一個人都是用的本身的身份證,不存在什麼公共身份證你們搶着用。數據結構
面試官:「噢,看來你對使用場景很明白嘛,那你講講要怎麼保證線程隔離的呢?」多線程
這個簡單啊,誰的數據就直接給它本身保管就行了嘛,對應的在 Java 中就是把線程隔離的數據存在這個線程對應的 Thread
實例中。併發
面試官:「具體一點?」spa
JDK 的作法是實際上是每個 Thread
類中都保存了一個 ThreadLocalMap
,以 ThreadLocal
爲 key,保存的數據爲 value, 這樣每一個線程就能有多個線程隔離的數據啦。如下面這段代碼爲例線程
public class Main {
private static ThreadLocal<String> id = new ThreadLocal<>();
private static ThreadLocal<String> phone = new ThreadLocal<>();
public static void main(String[] args) {
Thread t1 = new Thread(()-> {
id.set("2");
phone.set("654321");
});
t1.start();
id.set("1");
phone.set("123456");
}
}
複製代碼
對應的結構圖以下所示:code
所以,對 ThreadLocal
的讀寫,其實就是對 Thread
中的 ThreadLocalMap
的讀寫cdn
面試官:「能夠,你剛纔說到 Map 的讀寫了吧,那 ThreadLocalMap 和 HashMap 有什麼差異呢?」
須要注意的是 ThreadLocalMap
雖然也叫 Map,但並無繼承集合類中的 Map
接口,最大的差異再處理衝突上是不一樣的,集合類中的 Map 使用鏈地址法來處理衝突,而 ThreadLocalMap
使用的是開放尋址法,簡單的說就是若是衝突了,就找下一個索引的 Entry
,直到找到空的爲止,對應的代碼爲:(PS:若是學過數據結構的話應該知道散列表的幾種衝突處理,就不用看下面代碼啦)
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 使用hash來肯定索引
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
// 若是 tab[i] 已經被佔了,並且不是更新操做(即 k != key),那麼將索引+1,看看是否被佔了
// e = tab[i = nextIndex(i, len)]
// private static int nextIndex(int i, int len) {
// return ((i + 1 < len) ? i + 1 : 0);
// }
}
...
}
複製代碼
面試官:"看來你還看過一些源碼嘛,那 ThreadLocalMap 中 Entry 的 key 對 ThreadLocal 是弱引用你總應該知道的吧?"
固然,由於 ThreadLocal
實例通常有兩個引用地方,一個是聲明 ThreadLocal
的地方,還一個是 ThreadLocalMap
中 key 的引用,這就致使了一個問題,若是除了 ThreadLocalMap
外再也不有引用的話,那麼 ThreadLocal
實例仍是沒法釋放,可是這個實例也不再會被訪問到了,也就是內存泄露。所以將其設爲弱引用的話,若是沒有其餘外部強引用後也能被回收。
面試官:「雖然 ThreadLocal 被回收了,可是 Entry 還在啊,裏面的 value 引用也是個內存泄露啊!」
天然是有相應對策的,在調用 ThreadLocal
的 get()
、set()
等方法時可能會清除ThreadLocalMap
中 key 爲 null
的 Entry
對象。不過仍是建議在編程時使用 remove()
把不用了數據刪掉吧。另外就像開頭裏說的,建議是搭配 private static
使用的,畢竟卸載類仍是比較少見的吧,既然和線程同壽命,也就不存在泄露不泄露的問題了哈~
面試官:「嗯,那 ThreadLocal 就聊到這裏吧~」