【JavaSE】淺談TreadLocal,TreadLocal的常用方法set()、get()、remove()原始碼分析
1.TreadLocal是什麼
先來看一段程式碼:
public class Test {
private static String commStr;
private static ThreadLocal<String> threadStr = new ThreadLocal<String>();
public static void main(String[] args) {
commStr = "main";
threadStr.set("main");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
commStr = "thread";
threadStr.set("thread");
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(commStr); //thread
System.out.println(threadStr.get()); //main
}
}
在主類中建立了兩個靜態變數,一個String,另一個TreadLocal類建立的物件,雖然都是靜態的,但是輸出結果卻不是兩個thread(正確輸出結果在輸出語句後面註釋),說明普通的靜態變數,主執行緒和各個執行緒對它產生的改變都可以對其他執行緒產生影響。而TreadLocal類建立的物件對每個執行緒來說是獨立的,為每個執行緒建立了一個副本變數。用這種方法可以解決執行緒之間對某變數的訪問實際上是沒有依賴關係的,即一個執行緒不需要關心其他執行緒是否對這個變數進行了修改的。
2.TreadLocal的主要方法
public T get()
public void set(T value)
public void remove()
protected T initialValue()
- set(T value)方法用於設定副本變數的值
- get()方法用於獲取當前執行緒副本變數的值
- initialValue()為當前執行緒預設的副本變數值。
- remove()方法移除當前前程的副本變數值。
一、set(T value)方法
先來看一下原始碼:
①.getMap(t)方法
要設定副本變數值,就要先獲取此執行緒的ThreadLocalMap物件。
原始碼如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到這個方法很簡單,只是返回當前執行緒的threadLocals變數
ThreadLocal.ThreadLocalMap threadLocals = null;
可以看到在原始碼裡面,threadLocals變數的預設值是null。
所以我們可以得知,getMap(t)函式在當前執行緒第一次使用此方法的時候,返回的是null。
②.createMap()方法
此時我們走到了createMap方法,先看一下原始碼:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可以看到這時給threadLocals進行了賦值,再來看一下ThreadLocalMap(this, firstValue)方法是如何給threadLocals進行賦值的。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
這是一個有參構造方法,傳入的值是當前正在呼叫set方法的ThreadLocal物件this,還有一個就是要設定的副本變數值。方法內部先建立一個鍵值對的物件陣列,然後傳入的當前TreadLocal物件通過hash方法得到需要儲存的下標i,然後將這個鍵值對儲存到此下標的陣列處,建立成功size賦值為1,並且給出此hash的負載因子。通過這個方法建立一個TreadLocalMap的物件賦值給threadLocals,此時通過threadLocals就可以找到這個鍵值對陣列。
③.map的set方法
如果在第二步已經進行過第一次的createMap方法,此時getMap方法就可以得到非null的map,可以呼叫map的set方法將新的鍵值對新增到threadLLocals裡面。這裡對原始碼不做贅述。
二、get()方法
原始碼如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
getMap()方法上面已經說過,如果還沒用呼叫set方法,直接呼叫get方法,map會得到一個null的值。
①.setInitialValue()方法
當map為空時,此函式會返回setInitialValue()方法的返回值,先看一下原始碼:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
這裡除了initialValue()方法其他上面都說過了,直接看initialValue()方法是如果得到value副本變數值的。
protected T initialValue() {
return null;
}
恩,它很省事,直接返回一個null。那這樣導致setInitialValue()方法給get方法返回的也是一個null。
注意:如果ThreadLocal物件在沒有呼叫set()方法的時候,直接呼叫get()方法,會得到一個null,很可能產生空指標異常。
②.getEntry(this)方法
如果map不為空,我們自然要取得在當前執行緒的map(也就是treadLocals)裡面所儲存的和此TreadLocal對應的鍵值對,所以傳入this,看一下原始碼:
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
可以看到函式一開始就對此TreadLocal進行hash演算法,得到它所對應陣列下標i,然後判斷此下標處儲存的key是否和當前key一樣,如果一樣說明找到了正確的鍵值對,直接返回e。如果不一樣就呼叫getEntryAfterMiss(key, i, e)方法,這個方法原始碼裡面是使用線性探測的方法去找對應的key值,如果找不到就返回null。
如果返回了正確的Entry e,就將e的value值作為get的返回值。
三、remove()方法
原始碼如下:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
直接呼叫了ThreadLocalMap的remove方法。
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;
}
}
}
這個方法也是先找到當前ThreadLocal對應的hash下標i,然後通過線性探測的方式去找準確的key值,如果找到了。就用clear()方法將這個key值置為null,方便之後的 expungeStaleEntry(i)方法進行清理,expungeStaleEntry(i)方法不止清理了i位置上的entry,還把i之後的key為null的entry都清理了,並且順帶將一些有雜湊衝突的entry給填充回可用的index中。
小總結
到這裡ThreadLocal的remove方法也分析完了。remove方法的關鍵就在於主動斷開entry的key的引用連結,這樣在後續的expungeStaleEntry方法中,就會將這種key為null的entry給設定為null,方便GC對記憶體進行回收。
ThreadLocal的set,get和remove方法看下來,除了正常的功能之外,就是多了對key為null的entry的清理工作,方便GC回收這部分佔用的記憶體。expungeStaleEntry就是最核心的清理方法,這也是ThreadLocalMap的一種防範機制,因為ThreadLocalMap的生命週期和執行緒是一樣長的,不採取這種防範機制,是會造成記憶體洩漏的。如果多定義了幾個ThreadLocal物件,並且執行緒都將佔用記憶體比較大的物件給放到對應的執行緒中,可能就會造成OOM異常了。
四、 initialValue()方法
這個方法在第二個get()方法那裡提到過,主要是當某個TreadLocal還沒有進行set方法而直接呼叫get方法,返回的預設副本變數值,初始是null。可以覆寫這個方法,改變初始值。
3.ThreadLocal的應用場景
最常見的ThreadLocal使用場景為 用來解決 資料庫連線、Session管理等。
相關文章
- 【JavaSE】Map集合,HashMap的常用方法put、get的原始碼解析JavaHashMap原始碼
- 淺說 get set
- 【JDK原始碼分析】淺談HashMap的原理JDK原始碼HashMap
- 每日原始碼分析 - Lodash(remove.js)原始碼REMJS
- 快速生成get、set方法
- 淺談對屬性描述符__get__、__set__、__delete__的理解delete
- python淺談正則的常用方法Python
- 修改自動生成get/set方法模板程式碼
- JAVASE常用的類及其方法總結Java
- lombok get/set 與 JavaBean get/setLombokJavaBean
- Redis 中的 set 和 sorted set 如何使用,原始碼實現分析Redis原始碼
- 來談談限流-RateLimiter原始碼分析MIT原始碼
- 淺談.Net Core DependencyInjection原始碼探究原始碼
- java反射呼叫set和get方法的通用類Java反射
- Set介面和常用方法
- 淺談HTTP中Get與Post的區別HTTP
- Http協議中Get和Post的淺談HTTP協議
- 【PHP7原始碼分析】PHP7原始碼研究之淺談Zend虛擬機器PHP原始碼虛擬機
- 淺談Kotlin中的Sequences原始碼解析(十)Kotlin原始碼
- Android 淺談scrollTo和scrollBy原始碼Android原始碼
- 封裝中的get、set方法-學習筆記封裝筆記
- 分類不能自動建立 get set 方法
- Objective-C中get/set方法初探(1)Object
- Objective-C中get/set方法初探(2)Object
- java容器之Set常用方法Java
- 淺談HTTP中Get與Post的區別-javaHTTPJava
- ArrayList方法原始碼分析原始碼
- 面試官:來談談限流-RateLimiter原始碼分析面試MIT原始碼
- 淺談JFinal原始碼,短小精悍之道原始碼
- Linux核心原始碼分析之set_arch (一)Linux原始碼
- C# Get SetC#
- PHP memcached,(set,get)PHP
- linux原始碼分析方法Linux原始碼
- 淺談IntentService原理分析Intent
- Binder池淺談分析
- 走原始碼路線,淺談react的一些思路原始碼React
- 淺析 及整體分析 Relay 原始碼原始碼
- Redux原始碼createStore解讀常用方法Redux原始碼