Java線程之ThreadLocal解析
ThreadLocal實(shí)現(xiàn)原理
ThreadLocal.ThreadLocalMap
首先,每個(gè)Thread 里面都有一個(gè)成員 ThreadLocal.ThreadLocalMap 類型的成員變量
static class ThreadLocalMap { //內(nèi)部存儲其實(shí)是一個(gè) entry 的數(shù)組結(jié)構(gòu) private Entry[] table; }
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
看到這里我們應(yīng)該清楚了 ThreadLocal.ThreadLocalMap 的數(shù)據(jù)結(jié)構(gòu),如下圖
因?yàn)門hreadLocal.ThreadLocalMap 類型的變量 是Thread 的成員變量,所以其有線程隔離性.
那么ThreadLocal.ThreadLocalMap中的數(shù)據(jù)是從什么地方寫入或者讀取的呢?那時(shí)就ThreadLocal這個(gè)類所實(shí)現(xiàn)的功能了。
ThreadLocal
這里我們著重分析一個(gè) ThreadLocal的 get() 方法 、set(T value)方法 、 remove()方法
public T get() { //獲取當(dāng)前線程 Thread t = Thread.currentThread(); //獲取當(dāng)前線程的threadLocals 成員變量 ThreadLocalMap map = getMap(t); if (map != null) { //獲取以當(dāng)前TheadLocal對象為key 的key-value 鍵值對 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") //返回value T result = (T)e.value; return result; } } //否則調(diào)用setInitialValue方法返回默認(rèn)值 return setInitialValue(); 1 }
1 private T setInitialValue() { //我們可以重載該方法,初始化默認(rèn)值 T value = initialValue(); 2 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //創(chuàng)建ThreadLocalMap,并存儲到Thread 中 createMap(t, value); 3 return value; } //我們可以重載該方法,初始化默認(rèn)值 2 protected T initialValue() { return null; } //創(chuàng)建 3 void createMap(Thread t, T firstValue) { //賦值給當(dāng)前線程 t.threadLocals = new ThreadLocalMap(this, firstValue); }
set() 方法類似
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
remove()方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
threadLocal 使用風(fēng)險(xiǎn)
最大的風(fēng)險(xiǎn)就是產(chǎn)生的內(nèi)存泄漏風(fēng)險(xiǎn),
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
通過構(gòu)造函數(shù)我們知道 key 為弱引用,value 為強(qiáng)引用。
當(dāng)threadLocal變量被置為null時(shí),Heap中的threadLocal對象失去了引用,將被GC回收。同時(shí)Entry的key也將被回收。Entry中只剩下沒有key的Value,此時(shí)存在強(qiáng)引用鏈threadlocalmap–>Entry–>Value,若當(dāng)前線程遲遲不能結(jié)束,則Heap中的Value始終不能被GC回收,造成內(nèi)存泄漏。所以必須建議 最好的辦法在不使用ThreadLocal的時(shí)候,調(diào)用remove()方法,通過顯示 的設(shè)置value = null 清除數(shù)據(jù)。
為了避免內(nèi)存泄漏,ThreadLocalMap在調(diào)用get()方法和set()方法時(shí)操作數(shù)組時(shí),也會去調(diào)用expungeStaleEntry()方法來清除Entry中key為null的Value,但是這種清理是不及時(shí),因?yàn)槲覀儾槐WC時(shí)候還會觸發(fā)get()方法和set()等方法。因此也會引發(fā)內(nèi)存泄漏的風(fēng)險(xiǎn)。只有remove()方法,顯式調(diào)用expungeStaleEntry()方法,才是王道。
使用場景
場景1
線程內(nèi)保存全局變量,可以讓不同方法直接使用,避免參數(shù)傳遞麻煩,例如數(shù)據(jù)源切換
@Configuration public class DataSourceProxyConfig { //數(shù)據(jù)源1 @Bean("originOrder") @ConfigurationProperties(prefix = "spring.datasource.order") public DataSource dataSourceMaster() { return new DruidDataSource(); } //數(shù)據(jù)源2 @Bean("originStorage") @ConfigurationProperties(prefix = "spring.datasource.storage") public DataSource dataSourceStorage() { return new DruidDataSource(); } //數(shù)據(jù)源3 @Bean("originPay") @ConfigurationProperties(prefix = "spring.datasource.pay") public DataSource dataSourcePay() { return new DruidDataSource(); } //數(shù)據(jù)源4 @Bean(name = "order") public DataSourceProxy masterDataSourceProxy(@Qualifier("originOrder") DataSource dataSource) { return new DataSourceProxy(dataSource); } //數(shù)據(jù)源5 @Bean(name = "storage") public DataSourceProxy storageDataSourceProxy(@Qualifier("originStorage") DataSource dataSource) { return new DataSourceProxy(dataSource); } //數(shù)據(jù)源6 @Bean(name = "pay") public DataSourceProxy payDataSourceProxy(@Qualifier("originPay") DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean("dynamicDataSource") public DataSource dynamicDataSource(@Qualifier("order") DataSource dataSourceOrder, @Qualifier("storage") DataSource dataSourceStorage, @Qualifier("pay") DataSource dataSourcePay) { //動態(tài)數(shù)據(jù)源,這是spring 為我們提供的 DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(3); dataSourceMap.put(DataSourceKey.ORDER.name(), dataSourceOrder); dataSourceMap.put(DataSourceKey.STORAGE.name(), dataSourceStorage); dataSourceMap.put(DataSourceKey.PAY.name(), dataSourcePay); dynamicRoutingDataSource.setDefaultTargetDataSource(dataSourceOrder); //數(shù)據(jù)源以鍵值對的形式存儲 dynamicRoutingDataSource.setTargetDataSources(dataSourceMap); DynamicDataSourceContextHolder.getDataSourceKeys().addAll(dataSourceMap.keySet()); return dynamicRoutingDataSource; } @Bean @ConfigurationProperties(prefix = "mybatis") public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("dynamicDataSource") DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } }
public class DynamicRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { log.info("當(dāng)前數(shù)據(jù)源 [{}]", DynamicDataSourceContextHolder.getDataSourceKey()); //調(diào)用我們的ThreadLocal 獲取數(shù)據(jù)源的key return DynamicDataSourceContextHolder.getDataSourceKey(); } }
public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.ORDER::name); private static List<Object> dataSourceKeys = new ArrayList<>(); public static void setDataSourceKey(DataSourceKey key) { CONTEXT_HOLDER.set(key.name()); } public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } public static List<Object> getDataSourceKeys() { return dataSourceKeys; } }
在程序代碼中我們就可以使用DynamicDataSourceContextHolder.setDataSourceKey(),進(jìn)行數(shù)據(jù)源的切換了。
在業(yè)務(wù)代碼執(zhí)行完成后,記得顯示調(diào)用clearDataSourceKey()方法清除數(shù)據(jù)。
為了方便使用,我們完成一下,就是可以在需要切換數(shù)據(jù)源 Service 或 Mapper 方法上添加 @DataSource 注解,來實(shí)現(xiàn)數(shù)據(jù)源的切換功能
本實(shí)現(xiàn)出自ruoyi項(xiàng)目,感謝若依
聲明一個(gè)切面,攔截包含 @DataSource注解的方法
@Component public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" + "|| @within(com.ruoyi.common.annotation.DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (StringUtils.isNotNull(dataSource)) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { // 銷毀數(shù)據(jù)源 在執(zhí)行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } /** * 獲取需要切換的數(shù)據(jù)源 */ public DataSource getDataSource(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); if (Objects.nonNull(dataSource)) { return dataSource; } return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); } }
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource { /** * 切換數(shù)據(jù)源名稱 */ public DataSourceType value() default DataSourceType.MASTER; }
這樣就可以在方法上直接使用@DataSource 注解實(shí)現(xiàn)數(shù)據(jù)源的切換功能了。再次強(qiáng)調(diào),一定要顯示調(diào)用remove 方法確保內(nèi)存回收。
場景2
每個(gè)線程需要一個(gè)獨(dú)享的對象,比如非線程安全的工具類 例如SimpleDateFormt
class SimpleDateFormtHolder { private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyy-MM-dd"); } }; public static SimpleDateFormat getSimpleDateFormt() { return threadLocal.get(); } public static void setSimpleDateFormt(SimpleDateFormat simpleDateFormat) { threadLocal.set(simpleDateFormat); } public static void removeSimpleDateFormt() { threadLocal.remove(); } }
到此這篇關(guān)于Java線程之ThreadLocal解析的文章就介紹到這了,更多相關(guān)ThreadLocal解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)例解析觀察者模式及其在Java設(shè)計(jì)模式開發(fā)中的運(yùn)用
觀察者模式定義了一種一對多的依賴關(guān)系,讓多個(gè)觀察者對象同時(shí)監(jiān)聽某一個(gè)主題對象,這個(gè)主題對象在狀態(tài)上發(fā)生變化時(shí),會通知所有觀察者對象,使它們能夠自動更新自己.下面就以實(shí)例解析觀察者模式及其在Java設(shè)計(jì)模式開發(fā)中的運(yùn)用2016-05-05Java中的InputStreamReader和OutputStreamWriter源碼分析_動力節(jié)點(diǎn)Java學(xué)院整理
本文通過示例代碼給大家解析了Java中的InputStreamReader和OutputStreamWriter知識,需要的的朋友參考下吧2017-05-05Springboot如何通過yml配置文件為靜態(tài)成員變量賦值
這篇文章主要介紹了Springboot如何通過yml配置文件為靜態(tài)成員變量賦值,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10@Transaction,@Async在同一個(gè)類中注解失效的原因分析及解決
這篇文章主要介紹了@Transaction,@Async在同一個(gè)類中注解失效的原因分析及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12