
前言
- 在
Java多線程中,線程變量ThreadLocal非常重要,但對于很多開發(fā)者來說,這并不容易理解,甚至覺得有點神秘 - 今天,我將獻上一份
ThreadLocal的介紹 & 實戰(zhàn)攻略,希望你們會喜歡。
Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現(xiàn)Runnable接口使用(含實例教程)
復合使用
Android多線程:AsyncTask使用教程(含實例講解)
Android多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal
目錄

1. 簡介

2. 使用流程
主要是創(chuàng)建ThreadLocal變量 & 訪問ThreadLocal變量
2.1 創(chuàng)建ThreadLocal變量
共有3種方式,具體如下
// 1. 直接創(chuàng)建對象
private ThreadLocal myThreadLocal = new ThreadLocal()
// 2. 創(chuàng)建泛型對象
private ThreadLocal myThreadLocal = new ThreadLocal<String>();
// 3. 創(chuàng)建泛型對象 & 初始化值
// 指定泛型的好處:不需要每次對使用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類型的參數(shù)
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 {
// 創(chuàng)建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. 實現(xiàn)原理
- 核心原理
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里的值;否則創(chuàng)建1個ThreadLocalMap對象
if (map != null)
map.set(this, value);// 替換
else
createMap(t, value);// 創(chuàng)建->>分析2
}
/**
* 獲取ThreadLocal變量里的值
* 由于ThreadLocal變量引用 指向 ThreadLocalMap對象,即獲取ThreadLocalMap對象的值 = 該線程設置的存儲在ThreadLocal變量的值
**/
public T get() {
// 1. 獲得當前線程
Thread t = Thread.currentThread();
// 2. 獲取該線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 3. 若該線程的ThreadLocalMap對象已存在,則直接獲取該Map里的值;否則則通過初始化函數(shù)創(chuàng)建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對象已存在,則直接替換該值;否則則創(chuàng)建
if (map != null)
map.set(this, value); // 替換
else
createMap(t, value); // 創(chuàng)建->>分析2
return value;
}
/**
* 分析1:獲取當前線程的threadLocals變量引用
**/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 分析2:創(chuàng)建當前線程的ThreadLocalMap對象
**/
void createMap(Thread t, T firstValue) {
// 新創(chuàng)建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點 保證了線程間的數(shù)據(jù)訪問隔離,即線程安全
- 測試代碼
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 {
// 創(chuàng)建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 與同步機制的區(qū)別

6. 總結
- 本文全面講解了
Java多線程ThreadLocal的相關知識 - 下一篇文章我將對講解
Android多線程的相關知識,感興趣的同學可以繼續(xù)關注Carson_Ho的簡書
Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現(xiàn)Runnable接口使用(含實例教程)
復合使用
Android多線程:AsyncTask使用教程(含實例講解)
Android多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal
歡迎關注Carson_Ho的簡書
不定期分享關于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。
