欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java線程之ThreadLocal解析

 更新時(shí)間:2023年09月26日 09:13:10   作者:奮斗的小面包  
這篇文章主要介紹了Java線程之ThreadLocal解析,ThreadLocal 提供線程的局部變量,每個(gè)線程都可以通過get()和set()對局部變量進(jìn)行操作而不會對其他線程的局部變量產(chǎn)生影響,實(shí)現(xiàn)了線程之間的數(shù)據(jù)隔離,需要的朋友可以參考下

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)文章

最新評論