詳解Java中ThreadLocal類型及簡(jiǎn)單用法
1 基本概念
ThreadLocal類提供了線程局部變量。這些變量與普通變量的不同之處在于,每個(gè)訪問(wèn)一個(gè)變量(通過(guò)其get或set方法)的線程都有自己的、獨(dú)立初始化的變量副本。ThreadLocal實(shí)例通常是希望將狀態(tài)與線程關(guān)聯(lián)起來(lái)的類中的私有靜態(tài)字段(例如,用戶ID或事務(wù)ID)。
例如,下面的類生成每個(gè)線程本地的唯一標(biāo)識(shí)符。 線程的id在第一次調(diào)用ThreadId.get()時(shí)被賦值,并且在后續(xù)調(diào)用中保持不變。
public class ThreadId {
// 包含要分配的下一個(gè)線程ID的原子整數(shù)
private static final AtomicInteger nextId = new AtomicInteger(0);
// 包含每個(gè)線程ID的線程局部變量
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// 返回當(dāng)前線程的唯一ID,并在必要時(shí)賦值
public static int get() {
return threadId.get();
}
}
只要線程是活的并且ThreadLocal實(shí)例是可訪問(wèn)的,每個(gè)線程都持有一個(gè)對(duì)線程局部變量副本的隱式引用; 當(dāng)一個(gè)線程離開(kāi)后,它的所有線程本地實(shí)例副本都將被垃圾收集(除非存在對(duì)這些副本的其他引用)
2 簡(jiǎn)單使用
private static ThreadLocal<String> threadLocal = new ThreadLocal();
private static void print(String thread) {
//打印當(dāng)前線程中本地內(nèi)存中本地變量的值
System.out.println(thread + " :" + threadLocal.get());
//清除本地內(nèi)存中的本地變量
threadLocal.remove();
}
public static void main(String[] args) {
new Thread(() -> {
//設(shè)置線程1副本變量的值
threadLocal.set("I am Thread1");
//調(diào)用打印方法
print("thread1");
//打印本地變量
System.out.println("after remove : " + threadLocal.get());
}).start();
new Thread(() -> {
//設(shè)置線程2副本變量的值
threadLocal.set("I am Thread2");
//調(diào)用打印方法
print("thread2");
System.out.println("after remove : " + threadLocal.get());
}).start();
}
運(yùn)行結(jié)果:
thread1 :I am Thread1
thread2 :I am Thread2
after remove : null
after remove : null
由上邊的程序可以看出,ThreadLocal就是將本地變量在多線程訪問(wèn)條件下給每個(gè)線程一個(gè)副本變量,圖示:

3 應(yīng)用場(chǎng)景
最典型應(yīng)用場(chǎng)景就是Spring的聲明式事務(wù)、 解決數(shù)據(jù)庫(kù)連接、Session管理
那數(shù)據(jù)庫(kù)鏈接為例:
將數(shù)據(jù)庫(kù)的鏈接示例在每個(gè)線程中都有一份副本數(shù)據(jù),在某一個(gè)線程關(guān)閉連接的時(shí)候就不會(huì)關(guān)閉其他鏈接,session同理。
private static final ThreadLocal<Connection> connection = new ThreadLocal() {
@Override
protected Object initialValue() {
return Connection.getConnection();
}
};
private static Connection getConnections() {
return connection.get();
}
public static void main(String[] args) {
new Thread(() -> {
Connection connection = getConnections();
//不會(huì)關(guān)閉其他鏈接
connection.close();
}).start();
new Thread(() -> {
Connection connection = getConnections();
connection.close();
}).start();
}
4 底層原理
ThreadLocal類型主要有3個(gè)方法和一個(gè)數(shù)據(jù)結(jié)構(gòu),分別是get()、set(Object)、remove()及ThreadLocalMap
4.1 set(Object)
將該線程局部變量的當(dāng)前線程副本設(shè)置為指定的值。 大多數(shù)子類都不需要重寫(xiě)這個(gè)方法,只依賴于initialValue方法來(lái)設(shè)置線程局部變量的值。
參數(shù): Value -要存儲(chǔ)在當(dāng)前線程本地線程的副本中的值。
public void set(T value) {
//獲取調(diào)用者線程
Thread t = Thread.currentThread();
//以當(dāng)前線程作為key值,去查找對(duì)應(yīng)的線程變量,找到對(duì)應(yīng)的map
ThreadLocalMap map = getMap(t);
//如果map不為null,就直接添加本地變量,key為當(dāng)前定義的ThreadLocal變量的this引用,值為添加的本地變量值
if (map != null)
map.set(this, value);
//如果map為null,說(shuō)明首次添加,需要首先創(chuàng)建出對(duì)應(yīng)的map
else
createMap(t, value);
}
4.2 get()
返回該線程局部變量的當(dāng)前線程副本中的值。 如果變量在當(dāng)前線程中沒(méi)有值,則首先將其初始化為initialValue方法調(diào)用所返回的值。
返回: 這個(gè)線程本地的當(dāng)前線程值
public T get() {
Thread t = Thread.currentThread();
//獲取當(dāng)前線程的threadLocals變量
ThreadLocalMap map = getMap(t);
//如果threadLocals變量不為null,就可以在map中查找到本地變量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//執(zhí)行到此處,threadLocals為null,調(diào)用該更改初始化當(dāng)前線程的threadLocals變量
return setInitialValue();
}
private T setInitialValue() {
//protected T initialValue() {return null;}
T value = initialValue();
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//以當(dāng)前線程作為key值,去查找對(duì)應(yīng)的線程變量,找到對(duì)應(yīng)的map
ThreadLocalMap map = getMap(t);
//如果map不為null,就直接添加本地變量,key為當(dāng)前線程,值為添加的本地變量值
if (map != null)
map.set(this, value);
//如果map為null,說(shuō)明首次添加,需要首先創(chuàng)建出對(duì)應(yīng)的map
else
createMap(t, value);
return value;
}
4.3 remove()
移除此線程局部變量的當(dāng)前線程值。如果這個(gè)線程局部變量隨后被當(dāng)前線程讀取,它的值將通過(guò)調(diào)用它的initialValue方法重新初始化,除非它的值是由當(dāng)前線程在中間設(shè)置的。這可能導(dǎo)致在當(dāng)前線程中多次調(diào)用initialValue方法。
public void remove() {
//獲取當(dāng)前線程綁定的threadLocals
ThreadLocalMap m = getMap(Thread.currentThread());
//如果map不為null,就移除當(dāng)前線程中指定ThreadLocal實(shí)例的本地變量
if (m != null)
m.remove(this);
}
4.4 ThreadLocalMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
//有效Entry數(shù)組
private Entry[] table;
//大小
private int size = 0;
//負(fù)載因子
private int threshold; // Default to 0
}
5 內(nèi)存泄漏隱患和防止策略
5.1 為什么會(huì)發(fā)生內(nèi)存泄漏?
在線程池中線程的存活時(shí)間太長(zhǎng),往往都是和程序同生共死的,這樣 Thread 持有的 ThreadLocalMap 一直都不會(huì)被回收,再加上 ThreadLocalMap 中的 Entry 對(duì) ThreadLocal 是弱引用(WeakReference),所以只要 ThreadLocal 結(jié)束了自己的生命周期是可以被回收掉的。
Entry 中的 Value 是被 Entry 強(qiáng)引用的,即便 value 的生命周期結(jié)束了,value 也是無(wú)法被回收的,導(dǎo)致內(nèi)存泄露。
5.2 怎樣防止內(nèi)存泄漏?
- ThreadLocal申明為private static final xxx
- ThreadLocal使用后務(wù)必調(diào)用remove方法。
參考文章:
https://www.cnblogs.com/fsmly/p/11020641.html
https://blog.csdn.net/meism5/article/details/90413860
https://blog.csdn.net/zzg1229059735/article/details/82715741
到此這篇關(guān)于詳解Java中ThreadLocal類型及簡(jiǎn)單用法的文章就介紹到這了,更多相關(guān)Java中ThreadLocal類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java 中ThreadLocal類詳解
- 深入解析Java中ThreadLocal線程類的作用和用法
- Java多線程編程中ThreadLocal類的用法及深入
- 快速了解Java中ThreadLocal類
- Java ThreadLocal類應(yīng)用實(shí)戰(zhàn)案例分析
- JAVA開(kāi)發(fā)常用類庫(kù)UUID、Optional、ThreadLocal、TimerTask、Base64使用方法與實(shí)例詳解
- Java 超詳細(xì)講解ThreadLocal類的使用
- Java并發(fā)編程ThreadLocalRandom類詳解
- Java ThreadLocal類使用詳解
相關(guān)文章
Maven打包跳過(guò)測(cè)試的5種方式總結(jié)
使用Maven打包的時(shí)候,可能會(huì)因?yàn)閱卧獪y(cè)試打包失敗,這時(shí)候就需要跳過(guò)單元測(cè)試,下面這篇文章主要給大家介紹了關(guān)于Maven打包跳過(guò)測(cè)試的5種方式,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01
Java中if...else語(yǔ)句使用的學(xué)習(xí)教程
這篇文章主要介紹了Java中if...else語(yǔ)句使用的學(xué)習(xí)教程,是Java入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-11-11
Springcould多模塊搭建Eureka服務(wù)器端口過(guò)程詳解
這篇文章主要介紹了Springcould多模塊搭建Eureka服務(wù)器端口過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
使用maven-archetype-plugin現(xiàn)有項(xiàng)目生成腳手架的方法
這篇文章主要介紹了使用maven-archetype-plugin現(xiàn)有項(xiàng)目生成腳手架的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
SpringBoot整合DeepSeek技術(shù)指南(實(shí)際應(yīng)用場(chǎng)景)
這篇文章主要介紹了SpringBoot整合DeepSeek技術(shù)指南,本文通過(guò)實(shí)際應(yīng)用場(chǎng)景模擬給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-04-04
SpringBoot自動(dòng)裝配之@Enable深入講解
這篇文章主要介紹了SpringBoot自動(dòng)裝配之@Enable,SpringBoot中提供了很多Enable開(kāi)頭的注解,這些注解都是用于動(dòng)態(tài)啟用某些功能的。而其底層原理是使用@Import注?解導(dǎo)入一些配置類,實(shí)現(xiàn)Bean的動(dòng)態(tài)加載2023-01-01
詳解SpringSecurity如何實(shí)現(xiàn)前后端分離
這篇文章主要為大家介紹了詳解SpringSecurity如何實(shí)現(xiàn)前后端分離,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
JavaWeb中Servlet的生命周期及線程安全問(wèn)題詳解
這篇文章主要介紹了JavaWeb中Servlet的生命周期及線程安全問(wèn)題詳解,Servlet?生命周期可被定義為從創(chuàng)建直到毀滅的整個(gè)過(guò)程,Servlet體系結(jié)構(gòu)是建立在Java多線程機(jī)制之上的,它的生命周期是由Web容器負(fù)責(zé)的,需要的朋友可以參考下2024-01-01

