Java多執行緒:神祕的執行緒變數 ThreadLocal 你瞭解嗎?
阿新 • • 發佈:2019-02-18
前言
- 在
Java
多執行緒中,執行緒變數ThreadLocal
非常重要,但對於很多開發者來說,這並不容易理解,甚至覺得有點神祕 - 今天,我將獻上一份
ThreadLocal
的介紹 & 實戰攻略,希望你們會喜歡。
目錄
1. 簡介
2. 使用流程
主要是建立ThreadLocal
變數 & 訪問ThreadLocal
變數
2.1 建立ThreadLocal變數
共有3種方式,具體如下
// 1. 直接建立物件
private ThreadLocal myThreadLocal = new ThreadLocal()
// 2. 建立泛型物件
private ThreadLocal myThreadLocal = new ThreadLocal<String>();
// 3. 建立泛型物件 & 初始化值
// 指定泛型的好處:不需要每次對使用get()方法返回的值作強制型別轉換
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "This is the initial value";
}
};
// 特別注意:
// 1. ThreadLocal例項 = 類中的private、static欄位
// 2. 只需例項化物件一次 & 不需知道它是被哪個執行緒例項化
// 3. 每個執行緒都保持 對其執行緒區域性變數副本 的隱式引用
// 4. 執行緒消失後,其執行緒區域性例項的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)
// 5. 雖然所有的執行緒都能訪問到這個ThreadLocal例項,但是每個執行緒只能訪問到自己通過呼叫ThreadLocal的set()設定的值
// 即 哪怕2個不同的執行緒在同一個`ThreadLocal`物件上設定了不同的值,他們仍然無法訪問到對方的值
2.2 訪問ThreadLocal變數
// 1. 設定值:set()
// 需要傳入一個Object型別的引數
myThreadLocal.set("初始值”);
// 2. 讀取ThreadLocal變數中的值:get()
// 返回一個Object物件
String threadLocalValue = (String) myThreadLocal.get();
3. 具體使用
以下則是測試程式碼
public class ThreadLocalTest {
// 測試程式碼
public static void main(String[] args){
// 新開2個執行緒用於設定 & 獲取 ThreadLoacl的值
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "執行緒1").start();
new Thread(runnable, "執行緒2").start();
}
// 執行緒類
public static class MyRunnable implements Runnable {
// 建立ThreadLocal & 初始化
private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "初始化值";
}
};
@Override
public void run() {
// 執行執行緒時,分別設定 & 獲取 ThreadLoacl的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal"); // 設定值 = 執行緒名
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
- 測試結果
執行緒1:執行緒1的threadLocal
執行緒2:執行緒2的threadLocal
// 從上述結果看出,在2個執行緒分別設定ThreadLocal值 & 分別獲取,結果並未互相干擾
4. 實現原理
核心原理
ThreadLocal
類中有1個Map
(稱:ThreadLocalMap
):用於儲存每個執行緒 & 該執行緒設定的儲存在ThreadLocal
變數的值ThreadLocalMap
的鍵Key
= 當前ThreadLocal
例項、值value
= 該執行緒設定的儲存在ThreadLocal
變數的值- 該
key
是ThreadLocal
物件的弱引用;當要拋棄掉ThreadLocal
物件時,垃圾收集器會忽略該key
的引用而清理掉ThreadLocal
物件
關於如何設定 & 獲取
ThreadLocal
變數裡的值,具體請看下面的原始碼分析請直接看程式碼註釋
// ThreadLocal的原始碼
public class ThreadLocal<T> {
...
/**
* 設定ThreadLocal變數引用的值
* ThreadLocal變數引用 指向 ThreadLocalMap物件,即設定ThreadLocalMap的值 = 該執行緒設定的儲存在ThreadLocal變數的值
* ThreadLocalMap的鍵Key = 當前ThreadLocal例項
* ThreadLocalMap的值 = 該執行緒設定的儲存在ThreadLocal變數的值
**/
public void set(T value) {
// 1. 獲得當前執行緒
Thread t = Thread.currentThread();
// 2. 獲取該執行緒的ThreadLocalMap物件 ->>分析1
ThreadLocalMap map = getMap(t);
// 3. 若該執行緒的ThreadLocalMap物件已存在,則替換該Map裡的值;否則建立1個ThreadLocalMap物件
if (map != null)
map.set(this, value);// 替換
else
createMap(t, value);// 建立->>分析2
}
/**
* 獲取ThreadLocal變數裡的值
* 由於ThreadLocal變數引用 指向 ThreadLocalMap物件,即獲取ThreadLocalMap物件的值 = 該執行緒設定的儲存在ThreadLocal變數的值
**/
public T get() {
// 1. 獲得當前執行緒
Thread t = Thread.currentThread();
// 2. 獲取該執行緒的ThreadLocalMap物件
ThreadLocalMap map = getMap(t);
// 3. 若該執行緒的ThreadLocalMap物件已存在,則直接獲取該Map裡的值;否則則通過初始化函式建立1個ThreadLocalMap物件
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value; // 直接獲取值
}
return setInitialValue(); // 初始化
}
/**
* 初始化ThreadLocal的值
**/
private T setInitialValue() {
T value = initialValue();
// 1. 獲得當前執行緒
Thread t = Thread.currentThread();
// 2. 獲取該執行緒的ThreadLocalMap物件
ThreadLocalMap map = getMap(t);
// 3. 若該執行緒的ThreadLocalMap物件已存在,則直接替換該值;否則則建立
if (map != null)
map.set(this, value); // 替換
else
createMap(t, value); // 建立->>分析2
return value;
}
/**
* 分析1:獲取當前執行緒的threadLocals變數引用
**/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 分析2:建立當前執行緒的ThreadLocalMap物件
**/
void createMap(Thread t, T firstValue) {
// 新建立1個ThreadLocalMap物件 放入到 Thread類的threadLocals變數引用中:
// a. ThreadLocalMap的鍵Key = 當前ThreadLocal例項
// b. ThreadLocalMap的值 = 該執行緒設定的儲存在ThreadLocal變數的值
t.threadLocals = new ThreadLocalMap(this, firstValue);
// 即 threadLocals變數 屬於 Thread類中 ->> 分析3
}
...
}
/**
* 分析3:Thread類 原始碼分析
**/
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
// 即 Thread類持有threadLocals變數
// 執行緒類例項化後,每個執行緒物件擁有獨立的threadLocals變數變數
// threadLocals變數在 ThreadLocal物件中 通過set() 或 get()進行操作
...
}
5. 額外補充
5.1 ThreadLocal如何做到執行緒安全
- 每個執行緒擁有自己獨立的
ThreadLocals
變數(指向ThreadLocalMap
物件 ) - 每當執行緒 訪問
ThreadLocals
變數時,訪問的都是各自執行緒自己的ThreadLocalMap
變數(鍵 - 值) ThreadLocalMap
變數的鍵key
= 唯一 = 當前ThreadLocal
例項
上述3點 保證了執行緒間的資料訪問隔離,即執行緒安全
- 測試程式碼
public class ThreadLocalTest {
// 測試程式碼
public static void main(String[] args){
// 新開2個執行緒用於設定 & 獲取 ThreadLoacl的值
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "執行緒1").start();
new Thread(runnable, "執行緒2").start();
}
// 執行緒類
public static class MyRunnable implements Runnable {
// 建立ThreadLocal & 初始化
private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "初始化值";
}
};
@Override
public void run() {
// 執行執行緒時,分別設定 & 獲取 ThreadLoacl的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal"); // 設定值 = 執行緒名
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
- 測試結果
執行緒1:執行緒1的threadLocal
執行緒2:執行緒2的threadLocal
// 從上述結果看出,在2個執行緒分別設定ThreadLocal值 & 分別獲取,結果並未互相干擾
5.2 與同步機制的區別
6. 總結
- 本文全面講解了
Java
多執行緒ThreadLocal
的相關知識 - 下面我將繼續對
Android
中的知識進行深入講解 ,有興趣可以繼續關注Carson_Ho的安卓開發筆記