ThreadLocal深度解析

本文基於jdk1.8.0_66寫成html

0. ThreadLocal簡介

ThreadLocal能夠提供線程內的局部對象,合理的使用能夠避免線程衝突的問題
比方說SimpleDateFormat是線程不安全的,可是若是用ThreadLocal給每一個線程分配一個SimpleDateFormat對象,咱們就能夠安全的使用了數組

爲了便於理解,咱們能夠將ThreadLocal想象成一個Map,key爲當前線程,value爲存入的值
threadLocal.get() 等效於 map.get(Thread.currentThread())
threadLocal隱式的幫咱們完成了獲取當前線程的操做,使用起來更爲方便安全

 

1. naive的想法

如同簡介裏所說,ThreadLocal最直接的設計思路是:
在ThreadLocal內部維護一個Map,key爲當前線程,value爲存入的值
ThreadLocal.set(obj)等效於Map.set(Thread.currentThread(), obj)
ThreadLocal.get()等效於Map.get(Thread.currentThread())函數

這個想法的優勢是實現簡單,可是問題也不少:性能

a. 通常來講,一個ThreadLocal會與多個Thread關聯,而一個Thread只會與少數的ThreadLocal關聯。因此從ThreadLocal去尋找關聯的Thread,開銷比從Thread尋找關聯的ThreadLocal要大。
b. 若是一個Thread死掉了,爲了防止內存泄漏,全部ThreadLocal中與這個Thread關聯的value都要被釋放,這個過程是手動的,不優雅,並且開銷較大。線程

 

2. JDK1.8中的想法

每一個Thread各自維護一個名爲threadLocals的變量,其類型爲ThreadLocal.ThreadLocalMap
這個Map的key是ThreadLocal,value是關聯的值
在調用ThreadLocal.get/set時,先用Thread.currentThread()得到當前Thread,而後找到當前Thread的threadLocals域,再以當前ThreadLocal爲key找到對應的value並返回。設計

這樣作好處不少:orm

a. 通常來講,一個Thread只會與少數的幾個ThreadLocal關聯,那麼從Thread去尋找對應的ThreadLocal開銷是很小的
b. 若是一個Thread死掉了,那麼它所關聯的threadLocals也會被自動釋放,在很大程度上避免了內存泄漏的問題htm


3. Netty中的進一步改進

Netty自定義了一個名爲FastThreadLocal的東西
大概想法:
原版ThreadLocal中,在ThreadLocal.ThreadLocalMap中查找時,採用的是線性探測法,發生哈希碰撞時會致使查詢變慢
爲了不這一問題,Netty爲每一個FastThreadLocal都設置了獨一無二的編號,Thread能夠直接根據這個編號尋址
這樣作絕對不會有哈希碰撞,可是佔用的空間也相應變大了,也就是空間換時間的套路。對象


4. ThreadLocal與內存泄漏

ThreadLocal有個很微妙的地方在於,它在某些場景下,仍是會發生內存泄漏

若是咱們在函數裏定義了一個局部的ThreadLocal變量,主線程往裏面set了一個很大的對象Huge後就退出這個函數該幹嗎幹嗎去了
如今這個ThreadLocal連帶着Huge都是垃圾了,可是gc能回收他們嗎?

按照通常的思路,ThreadLocal和Huge都會被Thread自帶的threadLocals引用,因此都不會被回收。
可是JDK的做者比我機智不少了,他們把ThreadLocal.ThreadLocalMap.Entry弄成了弱引用(WeakReference),也就是說沒有引用的ThreadLocal對象是會在full gc中被回收的。
可是問題依然存在:雖然ThreadLocal被回收了,可是它關聯的Huge對象卻還在,這可如何是好?

JDK的做者此時又玩了個騷操做,在對ThreadLocal作set操做時,會去檢查ThreadLocal.ThreadLocalMap的底層數組,若是發現某個key是null了(ThreadLocal被gc了),它會把對應的value也設爲null,這樣Huge對象就能夠被釋放了。

可是爲了性能考慮,這個檢查操做不會遍歷整個底層數組,而是每次只掃描一小段,因此在某些特定的場景下,仍是會發生內存泄漏的。

相關文章
相關標籤/搜索