
上一節你弄懂了ThreadLocal是什么、它的基本使用方式、get方法的底層原理,這一節讓繼續深入研究下:
- ThreadLocal的set原始碼原理
- JVM的中的強參考、弱參考、軟參考、虛參考
- 弱參考在ThreadLocal的應用
- ThreadLocal記憶體泄漏問題分析
- ThreadLocal應用場景舉例
ThreadLocal set方法原始碼原理
ThreadLocal set方法原始碼原理
你有了閱讀threadLocal的get方法的經驗,set方法的原始碼會變得非常簡單,set原始碼如下所示:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
上面的脈絡是不是很清楚,相信我都不需要畫圖大家就能理解,和上一節get方法呼叫的setInitialValue幾乎一模一樣,只是沒有了initialValue()方法而已,
如果當前執行緒第一次使用threadLcoal.set(Obejct),(假設當前執行緒之前也沒有呼叫過get方法),就會創建一個默認大小為16的threadLocalMap,并且將key設為threadLocal物件,value設定為對應的某個Object,
如果是第二次set肯走的是map.set(this, value);這句話的分支,直接向當前執行緒的threadLocalMap中設定一個key-value對,
如下圖所示:

JVM中的強參考、弱參考、軟參考、虛參考基礎知識
JVM中的強參考、弱參考、軟參考、虛參考基礎知識
你還記得ThreadLocalMap這個每個Thread都有的本地變數嗎?這個Map中的核心的資料結構是一個Entry,代表了Key-Value對的資料,Key值是ThreadLocal物件,value是存盤的物件資料,代碼如下所示:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = https://www.cnblogs.com/fanmao/p/v;
}
}
}
這個Entry繼承了一個WeakReference的物件,如果熟悉JVM的同學,可能了解這個物件是什么,它被稱作弱參考,
在java中,物件參考可以強參考、軟參考、弱參考、虛參考四種,是jvm回收記憶體判斷的重要標準之一,下面我簡單給大家介紹下他們是什么,一般應用在什么場景,
強參考StrongReference,一般宣告的一個物件,都是強參考,使用場景,比如 Loan l = new Loan(); l就是一個強參考,gc如果發現一個物件被強參考指向,如果JVM空間不足的時候,就算OOM也不會回收它,
軟參考SoftReference,當JVM空間不夠的時候,gc會先回收軟參考的空間,使用場景:適合用于快取,
舉個例子:Andriod用Map快取位圖資料,
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
public void addBitmapToCache(String path) {
// 強參考的Bitmap物件
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 軟參考的Bitmap物件
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
// 添加該物件到Map中使其快取
imageCache.put(path, softBitmap);
}
弱參考WeakReference,只要gc發現了弱參考,就會回收掉它的空間,使用場景:ThreadLocalMap, WeakHashMap中的Entry,,
舉個例子:ThreadLocalMap中的entry,這個一會我們重點分析這里的原理,為什么這么做,
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = https://www.cnblogs.com/fanmao/p/v;
}
}
虛參考PhantomReference,這個參考在gc垃圾回收執行緒看來,就是沒有參考的意思,它的作用是幫助JVM管理直接記憶體DirectBuffer,經典的使用場景:NIO,
舉個例子:比如DirectBuffer中的Cleaner就是繼承了PhantomReference,
public abstract interface DirectBuffer {
public abstract long address();
public abstract java.lang.Object attachment();
public abstract sun.misc.Cleaner cleaner();
}
public class Cleaner extends java.lang.ref.PhantomReference {
private static final java.lang.ref.ReferenceQueue<java.lang.Object> dummyQueue;
//省略
}
上面這四種參考我給大家畫一個圖,更好理解,如下圖所示:

弱參考在ThreadLocal真能防止記憶體泄漏嗎?
弱參考在ThreadLocal真能防止記憶體泄漏嗎?
了解了Java中的四種參考的概念后,我們來看下ThreadLcoalMap中的Entry繼承了WeakReference,到底是為什么?我們來看如下一個場景,
一個執行緒thread使用threadLocal物件tl設定了一個value為30M的物件,之后tl=null,不再使用了,tl指向的區域threadLocal物件被gc回收,此時會如下圖所示:

這也就解釋了,為什么ThreadLocalMap的Entry中的key使用弱參考:
因為若是強參考,即使tl=null,key是強參考的話,仍會指向threadLocal,導致threadLocal不會被回收,造成記憶體泄漏,而使用了key弱參考的話,就不會有問題,當tl=null的時候,key是弱參考,gc會直接回收調threadLocal記憶體中的這個物件,雖然使用了弱參考,但是仍存在記憶體value指向的強參考,指向了一個堆中的物件,此時key對應的threadLocal已經回收,key=null,此時,也無法訪問到value了,
所以如果一個set的value如果不在使用或threadLoacal不在使用了,一定要通過remove方法來洗掉掉之前的key,不然這么使用不當,還是會造成記憶體泄漏,導致30M的這個vlaue不會被回收掉

ThreadLocal應用場景
ThreadLocal應用場景
最后給大家提幾個ThreadLocal的應用場景,你可以想一下,ThreadLocal具備這樣特性,可以用在哪里?
Spring 的Transaction機制中的ThreadLocal
最經典的場景就是Spring 的Transaction機制,將一個執行緒中的事務放入ThreadLocal中,可以在整個方法呼叫堆疊中隨時取出事務的資訊進行修改和操作,不會影響其他的執行緒的事務,
// TransactionAspectSupport.java
private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
new NamedThreadLocal<TransactionInfo>("Current aspect-driven transaction");
Log4j2等日志框架中的MDC
public class LogbackMDCAdapter implements MDCAdapter {
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
}
SpringCloud Sleuth的請求鏈路跟蹤
通過ThreadLocal傳遞Trace資料,值得一提的是,還通過之前提到過的Thread的另一個本地變數副本inheritableThreadLocal,在創建子執行緒的時候,會將父執行緒的inheritableThreadLocals繼承下來,這樣就實作了TraceContext在父子執行緒中的傳遞,代碼如下:
public static final class Default extends CurrentTraceContext{
ThreadLocal<TraceContext> DEFAULT = new ThreadLocal<>();
// Inheritable as Brave 3's ThreadLocalServerClientAndLocalSpanState was inheritable
static final InheritableThreadLocal<TraceContext> INHERITABLE = new InheritableThreadLocal<>();
final ThreadLocal<TraceContext> local;
}
HDFS edits_log的txId自增后放入執行緒本地副本
HDFS每次創建一個檔案,目錄等操作會記錄一條日志到edits_log中,每條edit_log都有一個txId,會把這個txId記錄到當前執行緒的txId方便在整個執行緒程序中隨時取用,和修改,
/**
* FSEditLog 維護元資料(檔案目錄樹)也叫命名空間的修改
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class FSEditLog implements LogsPurgeable {
// stores the most current transactionId of this thread.
private static final ThreadLocal<TransactionId> myTransactionId = new ThreadLocal<TransactionId>() {
@Override
protected synchronized TransactionId initialValue() {
return new TransactionId(Long.MAX_VALUE);
}
};
private long beginTransaaction() {
assert Thread.holdsLock(this);
txid++;
TransactionId id = myTransactionId.get();
id.txid = txid;
return now();
}
還有很多的場景可以使用,其實通過上面的幾個場景,你應該能發現,其實ThreadLocal最常用的2個場景就是:
1、 執行緒中,各個方法需要共享變數時使用,除了方法之間傳遞入參,通過ThreadLocal可以很方便的做到這一點,
2、 多執行緒操作時,防止并發沖突,保證執行緒安全,比如一般會拷貝一份資料到執行緒本地,自己修改本地變數,是執行緒安全的,
好了,今天的成長記就到這里,你可以在自己的公司遇到的專案中或者開源代碼中找一下或者留意一下,看看它們是怎么使用ThreadLocal的,
歡迎你在評論區,寫下你遇見的場景,
本文由博客群發一文多發等運營工具平臺 OpenWrite 發布
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/329975.html
標籤:Java
