前言
在 Java多線程中,線程變量ThreadLocal非常重要漂辐,但對于很多開發(fā)者來說,這并不容易理解棕硫,甚至覺得有點神秘
今天髓涯,我將獻上一份?ThreadLocal的介紹 & 實戰(zhàn)攻略,希望你們會喜歡哈扮。
目錄
1. 簡介
我自己是一個從事了6年的Java全棧工程師纬纪,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java滑肉、大數(shù)據(jù)面向?qū)ο蟮竭M階的框架知識都有整理哦包各,可以來我的主頁免費領(lǐng)取哦。
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();
// 3. 創(chuàng)建泛型對象 & 初始化值
// 指定泛型的好處:不需要每次對使用get()方法返回的值作強制類型轉(zhuǎn)換
private ThreadLocal myThreadLocal = new ThreadLocal() {
@Override
protected String initialValue() {
return "This is the initial value";
}
};
// 特別注意:
// 1. ThreadLocal實例 = 類中的private问畅、static字段
// 2. 只需實例化對象一次 & 不需知道它是被哪個線程實例化
// 3. 每個線程都保持 對其線程局部變量副本 的隱式引用
// 4. 線程消失后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)
// 5. 雖然所有的線程都能訪問到這個ThreadLocal實例六荒,但是每個線程只能訪問到自己通過調(diào)用ThreadLocal的set()設(shè)置的值
// 即 哪怕2個不同的線程在同一個`ThreadLocal`對象上設(shè)置了不同的值护姆,他們?nèi)匀粺o法訪問到對方的值
2.2 訪問ThreadLocal變量
// 1. 設(shè)置值: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個線程用于設(shè)置 & 獲取 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 threadLocal = new ThreadLocal(){
@Override
protected String initialValue() {
return "初始化值";
}
};
@Override
public void run() {
// 運行線程時,分別設(shè)置 & 獲取 ThreadLoacl的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal"); // 設(shè)置值 = 線程名
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
測試結(jié)果
線程1:線程1的threadLocal
線程2:線程2的threadLocal
// 從上述結(jié)果看出掏击,在2個線程分別設(shè)置ThreadLocal值 & 分別獲取卵皂,結(jié)果并未互相干擾
4. 實現(xiàn)原理
核心原理
ThreadLocal類中有1個Map(稱:ThreadLocalMap):用于存儲每個線程 & 該線程設(shè)置的存儲在ThreadLocal變量的值
ThreadLocalMap的鍵Key = 當(dāng)前ThreadLocal實例、值value = 該線程設(shè)置的存儲在ThreadLocal變量的值
該key是 ThreadLocal對象的弱引用砚亭;當(dāng)要拋棄掉ThreadLocal對象時灯变,垃圾收集器會忽略該 key的引用而清理掉ThreadLocal對象
關(guān)于如何設(shè)置 & 獲取 ThreadLocal變量里的值,具體請看下面的源碼分析
請直接看代碼注釋
// ThreadLocal的源碼
public class ThreadLocal {
...
/**
* 設(shè)置ThreadLocal變量引用的值
* ThreadLocal變量引用 指向 ThreadLocalMap對象捅膘,即設(shè)置ThreadLocalMap的值 = 該線程設(shè)置的存儲在ThreadLocal變量的值
* ThreadLocalMap的鍵Key = 當(dāng)前ThreadLocal實例
* ThreadLocalMap的值 = 該線程設(shè)置的存儲在ThreadLocal變量的值
**/
public void set(T value) {
// 1. 獲得當(dāng)前線程
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對象的值 = 該線程設(shè)置的存儲在ThreadLocal變量的值
**/
public T get() {
// 1. 獲得當(dāng)前線程
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. 獲得當(dāng)前線程
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:獲取當(dāng)前線程的threadLocals變量引用
**/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 分析2:創(chuàng)建當(dāng)前線程的ThreadLocalMap對象
**/
void createMap(Thread t, T firstValue) {
// 新創(chuàng)建1個ThreadLocalMap對象 放入到 Thread類的threadLocals變量引用中:
// a. ThreadLocalMap的鍵Key = 當(dāng)前ThreadLocal實例
// b. ThreadLocalMap的值 = 該線程設(shè)置的存儲在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對象 )
每當(dāng)線程 訪問 ThreadLocals變量時,訪問的都是各自線程自己的ThreadLocalMap變量(鍵 - 值)
ThreadLocalMap變量的鍵 key = 唯一 = 當(dāng)前ThreadLocal實例
上述3點 保證了線程間的數(shù)據(jù)訪問隔離林艘,即線程安全
測試代碼
public class ThreadLocalTest {
// 測試代碼
public static void main(String[] args){
// 新開2個線程用于設(shè)置 & 獲取 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 threadLocal = new ThreadLocal(){
@Override
protected String initialValue() {
return "初始化值";
}
};
@Override
public void run() {
// 運行線程時盖奈,分別設(shè)置 & 獲取 ThreadLoacl的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal"); // 設(shè)置值 = 線程名
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
測試結(jié)果
線程1:線程1的threadLocal
線程2:線程2的threadLocal
// 從上述結(jié)果看出,在2個線程分別設(shè)置ThreadLocal值 & 分別獲取狐援,結(jié)果并未互相干擾
5.2 與同步機制的區(qū)別
我自己是一個從事了6年的Java全棧工程師钢坦,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java啥酱、大數(shù)據(jù)面向?qū)ο蟮竭M階的框架知識都有整理哦爹凹,可以來我的主頁免費領(lǐng)取哦。