結構演進
早起JDK版本中,ThreadLocal內部結構是一個Map,線程為key,線程在“線程本地變量”中綁定的值為Value。每一個ThreadLocal實例擁有一個Map實例。(Key是線程,Value是值)
JDK8中,ThreadLocal內部結構發生了演進,雖然還是Map,但是擁有者變成了Thread實例,每一個Thread實例擁有一個Map實例。Map中的key變為ThreadLocal實例。(Key是ThreadLocal,Value是值)
新版ThreadLocalMap如圖:
每一個線程在獲取本地值時,都會將ThreadLocal實例作為Key從自己擁有的ThreadLocalMap中獲取值,別的線程無法訪問自己的ThreadLocalMap實例,達到相互隔離的目的。
新版優勢(減少了內存消耗)
(1)ThreadLocalMap存儲的鍵值對數量減少。早期版本的數量與線程個數強關聯;新版的Key是ThreadLocal實例,會比線程數少。
(2)早期版本ThreadLocalMap擁有者為ThreadLocal,在線程銷毀后,ThreadLocalMap仍然存在;新版的ThreadLocalMap擁有者為Thread,Thread實例銷毀后,ThreadLocalMap也會隨之銷毀,減少內存消耗。
Entry的Key需要使用弱引用
為什么不直接使用ThreadLocal實例作為Key呢?
public void funcA() {
ThreadLocal local = new ThreadLocal<Integer>();
local.set(100);
local.get();
}
當線程執行funA方法時,先新建一個local實例,這是強引用,調用set方法后會在內部新建Entry實例,Key是弱引用包裝指向的local實例。當線程執行完A方法后,方法棧幀被銷毀,強引用local的值也沒有了,但此時線程的ThreadLocalMap對應Entry的Key引用還指向的ThreadLocal實例都不能被GC回收,這將造成內存泄漏問題。
弱引用:對象只能生存到下一次垃圾回收之前。
由於ThreadLocalMap中Key是弱引用,下次GC發生時,可以把那些沒有強引用指向的ThreadLocal回收。並且key被回收后,其Entry的key值變為null。后續ThreadLocal的get、set方法被調用時,就會清除這些Key為null的Entry,完成內存釋放。
使用state final修飾ThreadLocal對象
ThreadLocal實例作為ThreadLocalMap的Key,針對一個線程內的所有操作是共享的,使用static修飾ThreadLocal節約空間。
應用場景
場景一:線程隔離
這個的典型應用就是“數據庫連接獨享”。下面的代碼來自Hibernate,代碼通過ThreadLoacl進行數據庫連接(Session)的線程本地化存儲。
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() {
Session s = (Session) threadSession.get();
if(s == null) {
s = getSession();
threadSession.set(s);
}
return s;
}
一個Session代表了一個數據庫連接。通過以上代碼可以看出,這個Session相當於線程的私有變量,不是所有線程共用的,其他線程是獲取不到這個Session的。
一般來說,完成數據庫操作之后程序會將Session關閉,節省資源。如果Session為共享的方式,如果某個線程將Session關閉,其他線程在操作Session時就會報錯。所以通過ThreadLocal簡單實現了數據庫連接的安全使用。
Re
《Java高並發核心編程》