ThreadLocal概述
- ThreadLocal提供線程一個獨立的局部變量,解決了變量併發訪問的衝突問題。
- 解決了線程安全的問題
- ThreadLocal 對比給Thread上synchronized同步機制:
前者空間換時間、後者時間換空間
Threadlocal主題思維結構圖
核心部分會單獨拿出來畫圖、分析!
Threadlocal使用
@RequiresApi(api = Build.VERSION_CODES.O)
public class ThreadLocalTest {
static ThreadLocal<String> local = ThreadLocal.withInitial(() -> {
return "默認初始值";
});
static ThreadLocal local1 = new ThreadLocal();
public static void main(String[] args) {
System.out.print("local = " + local.get()+'\n');
local.set("this Local");
local1.set("this Local1111");
// local.remove();
System.out.print("Local1 = "+local1.get() + " set Local = " + local.get());
}
}
Log:
核心函數(對外 public/protected)
initialValue
當前線程初始副本變量值,一般由子類繼承ThreadLocal重寫,return一個默認值
get
:返回此線程局部變量在當前線程副本中的值
public T get() {
Thread t = Thread.currentThread();
//1.根據當前線程調用getMap()獲取Thread內部的ThreadLocal.ThreadLocalMap
ThreadLocalMap map = getMap(t);
//2 map不爲null,從map中獲取當前ThreadLocal的存儲實體對象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 3 map爲null,調用setInitialValue
// 3.1 map不存在,此線程中沒用維護的ThreadLocalMap對象
// 3.2 map存在,但是沒有與ThreadLocal關聯的ThreadLocalMap.Entry
return setInitialValue();
}
private T setInitialValue() {
//1. 獲取初始化線程變量副本
// 子類重寫 initialValue
T value = initialValue();
//2.根據當前線程調用getMap()獲取Thread內部的ThreadLocal.ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//2.1 存在存入map
if (map != null)
map.set(this, value);
// 2.2 不存在創建map,並存入
else
createMap(t, value);
return value;
}
set
public void set(T value) {
// 獲取當前線程
Thread t = Thread.currentThread();
//獲取當前線程的TreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 調用ThreadLocalMap的內部方法set(set後面會ThreadLocalMap分析中講解)
map.set(this, value);
// 不存在創建map,並存入
else
createMap(t, value);
}
// 創建與ThreadLocal關聯的ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
remove
:移除當前前程的副本變量值
通過getMap獲取當前線程的ThreadLoaclMap,不爲null調用ThreadLoaclMap內部的remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap
set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 這裏是爲了解決Hash衝突問題
for (Entry e = tab[i];//獲取Entry對象
e != null;// 不爲null
e = tab[i = nextIndex(i, len)]) {
//i = nextIndex(i, len) 得到一個key值的元素的位置
ThreadLocal<?> k = e.get();
// 如果是同一個對象,最新的value會覆蓋掉舊value
if (k == key) {
e.value = value;
return;
}
// 如果key爲null,則替換它的位置
if (k == null) {
// 替換掉舊值
replaceStaleEntry(key, value, i);
// prevIndex(int i, int len)在此方法中調用
return;
}
// 否則往後一個位置找,直到找到空的位置
}
// 上面的for循環主要作用:出現哈希衝突時,1:看是否是同一個對象或者是是否可替換,
// 2:否則往後移動一位,繼續判斷
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
remove
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 如果存在這個對象,刪除
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
總結
Thread內部結構:
組成
- 每個Thread內部有一個Map(ThreadLocalMap)
- Map裏存儲(Key)ThreadLocal,和(value)線程變量副本
- Thread內部的map由ThreadLocal維護,由ThreadLocal負責向map 設置/獲取線程變量值
- 形成線程隔離
Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- 存儲的key(ThreadLocal),爲弱引用
- 設計弱引用將對象生命週期和生命週期解綁
問題----------->
-
設計好處?
節省內存的使用,Thread銷燬,內部的map也隨之銷燬 -
和synchronized比較?
synchronized: 時間換空間
ThreadLocal:空間換時間 -
ThreadLocal內存泄漏的真實原因?
ThreadLocalMap的生命週期和Thread一樣長,如果沒有手動刪除key,就會造成內存泄漏。在使用完ThreadLocal,調用remove刪除Entry
-
爲什麼使用弱引用?
在使用完ThreadLocal後忘記調用remove,使用弱引用多一層保障:弱引用的ThradLocal被回收時,對應的value在下一次ThrealLocal調用get、set、remove時,舊元素會被清除,從而避免內存泄漏
。 -
hashCode衝突?
// 是一個提供原子操作的Integer類,通過線程安全的方式操作加減
private static AtomicInteger nextHashCode =
new AtomicInteger();
// HASH_INCREMENT = 0x61c88647 (16進制)目的哈希碼均勻的分佈在2的n次方的entry數組裏減少hash衝突
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 減少hash衝突的次數
key.threadLocalHashCode & (len - 1);