文章目錄
多個線程同時讀寫同一共享變量存在併發問題, 避免共享,就能避免併發問題。
1. ThreadLocal的使用方法
static class ThreadId {
static final AtomicLong nextId = new AtomicLong(0);
// 定義ThreadLocal變量
static final ThreadLocal<Long> tl =
ThreadLocal.withInitial(() -> nextId.getAndIncrement());
// 此方法會爲每個線程分配一個唯一的Id
static long get() {
return tl.get();
}
}
- 一個線程前後兩次調用ThreadId的get()方法,兩次的返回值是相同的;
- 兩個線程分別調用ThreadId的get()方法,那麼兩個線程看到的get()方法的返回值是不同的。
SimpleDateFormat不是線程安全的,在併發場景下可以用ThreadLocal解決線程安全問題。不同線程並不共享SimpleDateFormat,就像局部變量一樣,所以線程安全。
static class SafeDateFormat {
// 定義ThreadLocal變量
static final ThreadLocal<DateFormat> tl = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
static DateFormat get() {
return tl.get();
}
}
2. ThreadLocal的工作原理
2.1 自己實現的ThreadLocal
class MyThreadLocal<T> {
Map<Thread, T> locals = new ConcurrentHashMap<>();
// 獲取線程變量
T get() {
return locals.get(Thread.currentThread());
}
// 設置線程變量
void set(T t) {
locals.put(Thread.currentThread(), t);
}
}
直接創建一個Map,線程爲key,線程擁有的變量爲value。
2.2 java實現ThreadLocal
class Thread {
// 內部持有ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals;
}
class ThreadLocal<T> {
public T get() {
// 首先獲取線程持有的
// ThreadLocalMap
ThreadLocalMap map = Thread.currentThread().threadLocals;
// 在ThreadLocalMap中
// 查找變量
Entry e = map.getEntry(this);
return e.value;
}
static class ThreadLocalMap {
// 內部是數組而不是Map
Entry[] table;
// 根據ThreadLocal查找Entry
Entry getEntry(ThreadLocal key) {
// 省略查找邏輯
}
// Entry定義
static class Entry extends WeakReference<ThreadLocal> {
Object value;
}
}
}
Java的實現裏面也有一個Map,叫做ThreadLocalMap,不過持有ThreadLocalMap的不是ThreadLocal,而是Thread。Thread這個類內部有一個私有屬性threadLocals,其類型就是ThreadLocalMap,ThreadLocalMap的Key是ThreadLocal。
我們的設計裏面Map屬於ThreadLocal,而Java的實現裏面ThreadLocalMap則是屬於Thread。這兩種方式哪種更合理呢?很顯然Java的實現更合理一些。在Java的實現方案裏面,ThreadLocal僅僅是一個代理工具類,內部並不持有任何與線程相關的數據,所有和線程相關的數據都存儲在Thread裏面,這樣的設計容易理解。而從數據的親緣性上來講,ThreadLocalMap屬於Thread也更加合理。
還有一個原因,不容易產生內存泄露. 在我們的設計方案中,ThreadLocal持有的Map會持有Thread對象的引用,這就意味着,只要ThreadLocal對象存在,那麼Map中的Thread對象就永遠不會被回收。ThreadLocal的生命週期往往都比線程要長,所以這種設計方案很容易導致內存泄露。而Java的實現中Thread持有ThreadLocalMap,而且ThreadLocalMap裏對ThreadLocal的引用還是弱引用(WeakReference),所以只要Thread對象可以被回收,那麼ThreadLocalMap就能被回收。Java的這種實現方案雖然看上去複雜一些,但是更加安全。
3. ThreadLocal與內存泄露
在線程池中使用ThreadLocal可能導致內存泄露,
// TODO 作者寫的不是很好理解,待補
4. InheritableThreadLocal與繼承性
不建議使用。
5. 總結
線程本地存儲模式本質上是一種避免共享的方案。
6. 課後思考
實際工作中,有很多平臺型的技術方案都是採用ThreadLocal來傳遞一些上下文信息,例如Spring使用ThreadLocal來傳遞事務信息。我們曾經說過,異步編程已經很成熟了,那你覺得在異步場景中,是否可以使用Spring的事務管理器呢?