栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java多线程学习之【ThreadLocal】

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java多线程学习之【ThreadLocal】

目前卷文化盛行,为了增强面试能力,开始了无边无际的学习,无边界不是重点,重点是要深入1万米。言归正传,本文打算做一个多线程学习的系列文章,沉淀自我。

文章目录
  • 前言
  • 一、ThreadLocal是什么?
  • 二、使用方法
  • 三、应用场景
    • 3.1动态多源数据库
  • 四、源码剖析
  • 五、和其他工具比较


前言

本文主要是讲解ThreadLocal的概念,基本用法,使用场景,底层代码原理剖析。

一、ThreadLocal是什么?

ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

按照源码注释,翻译成中文,就是以下内容:

  • 这个类提供了线程局部变量。这些变量不同于正常的线程中的变量,每一个线程访问一个(通过get set方法)都有自己的独立的初始化变量的副本。线程本地实例通常是私有静态字段,希望把状态与线程关联(例如,用户ID或事务ID)。
private static final ThreadLocal contextHolder = new ThreadLocal<>();

只要线程还活着和线程本地实例可访问,每一个线程持有一个隐式引用线程局部变量的副本(ThreadLocalMap);线程消失后,所有的线程本地副本实例受到垃圾收集(除非存在这些副本的其他引用)。

package com.valley.jdk.threadlocal;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;


public class ThreadId {

    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal threadId = new ThreadLocal() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }

    public static void main(String[] args) throws InterruptedException {
        List tds=new ArrayList<>();
        for(int i=0;i<10;i++){
            Thread thread=new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("id="+get());
                }
            });
            tds.add(thread);
        }

        for(Thread td:tds){
            td.start();
        }

        for(Thread td:tds){
            td.join();
        }
    }

}

二、使用方法

ThreadLocal 提供了一些方法:

方法说明
createMap(Thread t, T firstValue)创建线程相关的线程本地,注意这个Map的key是线程本地对象,而不是线程对象
get()返回ThreadLocal变量在这个当前线程中副本的值。
getMap(Thread t)返回线程的ThreadLocalMap。
initialValue()初始化重写的方法。
remove()删除当前线程的ThreadLocal变量
set(T value)设置当前线程这个ThreadLocal变量设置为指定的值
setInitialValue()设初始化set
三、应用场景

ThreadLocal使用在动态数据源应用上,特别是涉及到很多的数据库时,一定会用到。

3.1动态多源数据库

定义线程本地对象

  • 代码如下:
public class DataSourceContextHolder {
	private static final ThreadLocal contextHolder = new ThreadLocal<>();

    public static synchronized void setDBType(String dbType){
        contextHolder.set(dbType);
    }

    public static String getDBType(){
        return contextHolder.get();
    }

    public static void clearDBType(){
        contextHolder.remove();
    }
}

定义动态数据源类,继承AbstractRoutingDataSource类

  • 代码如下:
import java.util.HashMap;
import java.util.Map;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


public class DynamicDataSource extends AbstractRoutingDataSource{
	private static DynamicDataSource instance;
    private static byte[] lock=new byte[0];
    private static Map dataSourceMap=new HashMap();

    @Override
    public void setTargetDataSources(Map targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        dataSourceMap.putAll(targetDataSources);
        super.afterPropertiesSet();// 必须添加该句,否则新添加数据源无法识别到
    }

    public Map getDataSourceMap() {
        return dataSourceMap;
    }

    public static synchronized DynamicDataSource getInstance(){
        if(instance==null){
            synchronized (lock){
                if(instance==null){
                    instance=new DynamicDataSource();
                }
            }
        }
        return instance;
    }
    //必须实现其方法
    @Override
    protected Object determineCurrentLookupKey() {
        String string = DataSourceContextHolder.getDBType();
        if (string != null){
            return string;
        }else return "default";

    }

}

默认数据源配置DataSourceConfig

  • 代码如下:
@Configuration
public class DataSourceConfig {
	 	@Value("${spring.datasource.default.url}")
	    private String defaultDBUrl;
	    @Value("${spring.datasource.default.username}")
	    private String defaultDBUser;
	    @Value("${spring.datasource.default.password}")
	    private String defaultDBPassword;
	    @Value("${spring.datasource.default.driverClassName}")
	    private String defaultDBDreiverName;

	    @Bean
	    public DynamicDataSource dynamicDataSource() {
	        DynamicDataSource dynamicDataSource = DynamicDataSource.getInstance();

	        DruidDataSource defaultDataSource = new DruidDataSource();
	        defaultDataSource.setUrl(defaultDBUrl);
	        defaultDataSource.setUsername(defaultDBUser);
	        defaultDataSource.setPassword(defaultDBPassword);
	        defaultDataSource.setDriverClassName(defaultDBDreiverName);


	        Map map = new HashMap<>();
	        map.put("default", defaultDataSource);
	        dynamicDataSource.setTargetDataSources(map);
	        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);

	        return dynamicDataSource;
	    }

	    @Bean
	    public SqlSessionFactory sqlSessionFactory(
	            @Qualifier("dynamicDataSource") DataSource dynamicDataSource)
	            throws Exception {
	        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
	        bean.setDataSource(dynamicDataSource);
	        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper

        DataSourceContextHolder.setDBType("default");

        List list = rulesService.selectId();
        if (list.size() > 0){
            for (String datasourceId:list) {
                TdatasourcesRules rules = rulesService.selectByKey(datasourceId);
                if (rules != null || !rules.equals("")){
                    String url = "jdbc:mysql://" + rules.getIp() + ":" + rules.getPort() + "/" + rules.getDatabaseName() + "?useUnicode=true&characterEncoding=utf8";

                    DruidDataSource dynamicDataSource = new DruidDataSource();
                    dynamicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
                    dynamicDataSource.setUrl(url);
                    dynamicDataSource.setUsername(rules.getUsername());
                    dynamicDataSource.setPassword(rules.getPassword());

                    
                    Map dataSourceMap = DynamicDataSource.getInstance().getDataSourceMap();
                    dataSourceMap.put(datasourceId, dynamicDataSource);
                    DynamicDataSource.getInstance().setTargetDataSources(dataSourceMap);
                }
            }
        }
    }
}

具体使用切换数据源方法

  • 代码如下:
    public void doEsAndDataTask(String dbType){

        //切换动态数据源
        DataSourceContextHolder.setDBType(dbType);
        List list = didMsgService.queryPart();

		//清除线程本地变量的副本,一般数据库链接都会用到线程池,所以要清除
        DataSourceContextHolder.clearDBType();
       
    }

重点:除了每个线程维护一个ThreadLocalMap,我们这里也需要维护一个数据源对象Map,数据源对象Map的key是我们的每个数据库ID,ThreadLocalMap的值是数据库ID。具体两者的映射逻辑交给了Spring-jdbc来处理的。
在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收,避免内存泄漏。
所以setDBType和clearDBType是成对出现。

四、源码剖析

ThreadLocal的初始化可以匿名内部类重写initialValue方法,也可以直接定义。

	private static final ThreadLocal contextHolder = new ThreadLocal<>();

我们通过获取本地变量,来剖析下源码

  • 获取ThreadLocalMap,然后通过ThreadLocal对象this作为Key获取值返回
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
  • getMap的参数是当前线程,返回了Map,类似透传的方法。从这里可以看出每个线程都有一个ThreadLocalMap。ThreadLocalMap是在ThreadLocal定义的一个静态内部类。
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
  • 设置初始化值,可以更清楚地上面的结论。
   private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
五、和其他工具比较
  • ThreadLocal与InheritableThreadLocal区别

InheritableThreadLocal继承ThreadLocal,InheritableThreadLocal使用优先于ThreadLocal的场景是,被保存在变量的每个线程的属性(例如,用户ID、事务ID)必须自动传送到创建的任何子线程。

  • ThreadLocal与Thread,ThreadLocalMap之间的关系

ThreadLocalMap其实是Thread线程的一个属性值,而ThreadLocal是维护ThreadLocalMap这个属性值的一个工具类。Thread线程可以拥有多个ThreadLocal维护的自己线程独享的变量。

  • ThreadLocal与Synchronized的区别

ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别:

  • Synchronized用于线程间的数据共享,而ThreadLocal用于线程间的数据隔离。
  • Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1024711.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号