线程不隔离 变量获取数据窜了
/*** @author meikb* @desc 线程不隔离 变量获取数据窜了* 通过使用ThreadLocal让线程变量实现隔离* @date 2020-05-19 19:06*/public class ThreadLocal1 {private String context;ThreadLocal<String> local = new ThreadLocal<String>();public String getContext() {return local.get();// return context;}public void setContext(String context) {local.set(context);// this.context = context;}public static void main(String[] args) {fun1();}public static void fun1(){ThreadLocal1 tt = new ThreadLocal1();for (int i = 0; i < 5; i++) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {tt.setContext(Thread.currentThread().getName() + "data");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "-------->" + tt.getContext());}});t.setName("线程" + i);t.start();}}}
使用synchronzied 也可以解决
public class ThreadLocal2 {private String context;public String getContext() {return context;}public void setContext(String context) {this.context = context;}public static void main(String[] args) {fun1();}public static void fun1(){ThreadLocal2 tt = new ThreadLocal2();for (int i = 0; i < 5; i++) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {synchronized (ThreadLocal2.class){tt.setContext(Thread.currentThread().getName() + "data");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "-------->" + tt.getContext());}}});t.setName("线程" + i);t.start();}}}
使用synchronized 可以解决线程变量独立问题,但是实际上是时间换取空间,每个线程顺序执行,所以效率比较低下。
使用场景
- 再jdbc的事务回滚场景中,Service要调用dao的方法,在service 加上事务,但是dao要和Service的connection
一致才能回滚,这个可以把connecttion存放在threadlocal中,这样事务肯定能回滚
- 多数据源场景下,如果多数据源的变量存储再普通string对象中,就可能再多线程的情况下,一个线程获取到另一个线程的数据源,导致数据源错乱。
多数据源实战
多数据源使用spring提供的一个非要好用的一个类 AbstractRoutingDataSource
private Map<Object, Object> targetDataSources; //存储的是从数据源的map 传入不同的keyprivate Object defaultTargetDataSource; //默认数据源//通过这个方法 设置不同的key 就可以切换不同的数据源protected Object resolveSpecifiedLookupKey(Object lookupKey) {return lookupKey;}
1.定义全局变量类
public class DynamicDataSourceContextHolder {private Logger logger = Logger.getLogger(DynamicDataSourceContextHolder.class);//存放当前线程使用的数据源类型信息 防止高并发 线程获取数据源错乱private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();//存放数据源idpublic static List<String> dataSourceIds = new ArrayList<String>();//设置数据源public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}//获取数据源public static String getDataSourceType() {return contextHolder.get();}//清除数据源public static void clearDataSourceType() {contextHolder.remove();}//判断当前数据源是否存在public static boolean isContainsDataSource(String dataSourceId) {return dataSourceIds.contains(dataSourceId);}}
2.继承 AbstractRoutingDataSource类 使用 通过Threadlocal get放当前线程的key
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}}
3.定义注解切面,不同的注解使用不同的key
@Aspect@Order(-1)//保证在@Transactional之前执行@Componentpublic class DynamicDattaSourceAspect {private Logger logger = Logger.getLogger(DynamicDattaSourceAspect.class);//改变数据源@Before("@annotation(targetDataSource)")public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {String dbid = targetDataSource.name();if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) {//joinPoint.getSignature() :获取连接点的方法签名对象logger.error("数据源 " + dbid + " 不存在使用默认的数据源 -> " + joinPoint.getSignature());} else {logger.debug("使用数据源:" + dbid);DynamicDataSourceContextHolder.setDataSourceType(dbid);}}@After("@annotation(targetDataSource)")public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {logger.debug("清除数据源 " + targetDataSource.name() + " !");DynamicDataSourceContextHolder.clearDataSourceType();}}
4.开始的时候注册默认数据源和 数据源map
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {private Logger logger = Logger.getLogger(DynamicDataSourceRegister.class);//指定默认数据源(springboot2.0默认数据源是hikari如何想使用其他数据源可以自己配置)private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";//默认数据源private DataSource defaultDataSource;//用户自定义数据源private Map<String, DataSource> slaveDataSources = new HashMap<>();@Overridepublic void setEnvironment(Environment environment) {initDefaultDataSource(environment);initslaveDataSources(environment);}private void initDefaultDataSource(Environment env) {// 读取主数据源Map<String, Object> dsMap = new HashMap<>();dsMap.put("driver", env.getProperty("spring.datasource.driver"));dsMap.put("url", env.getProperty("spring.datasource.url"));dsMap.put("username", env.getProperty("spring.datasource.username"));dsMap.put("password", env.getProperty("spring.datasource.password"));defaultDataSource = buildDataSource(dsMap);}private void initslaveDataSources(Environment env) {// 读取配置文件获取更多数据源String dsPrefixs = env.getProperty("slave.datasource.names");for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源Map<String, Object> dsMap = new HashMap<>();dsMap.put("driver", env.getProperty("spring.datasource." + dsPrefix + ".driver"));dsMap.put("url", env.getProperty("spring.datasource." + dsPrefix + ".url"));dsMap.put("username", env.getProperty("spring.datasource." + dsPrefix + ".username"));dsMap.put("password", env.getProperty("spring.datasource." + dsPrefix + ".password"));DataSource ds = buildDataSource(dsMap);slaveDataSources.put(dsPrefix, ds);}}//把DynamicDataSource 注入到spring 容器,并初始化属性,也可以在继承AbstractRoutingDataSource//初始化父类的targetDataSources 和 defaultTargetDataSource 参数@Overridepublic void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {Map<Object, Object> targetDataSources = new HashMap<Object, Object>();//添加默认数据源targetDataSources.put("dataSource", this.defaultDataSource);DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");//添加其他数据源targetDataSources.putAll(slaveDataSources);for (String key : slaveDataSources.keySet()) {DynamicDataSourceContextHolder.dataSourceIds.add(key);}//创建DynamicDataSourceGenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(DynamicDataSource.class);beanDefinition.setSynthetic(true);MutablePropertyValues mpv = beanDefinition.getPropertyValues();mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);mpv.addPropertyValue("targetDataSources", targetDataSources);//注册 - BeanDefinitionRegistrybeanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);logger.info("Dynamic DataSource Registry");}public DataSource buildDataSource(Map<String, Object> dataSourceMap) {try {Object type = dataSourceMap.get("type");if (type == null) {type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource}// Class<? extends DataSource> dataSourceType;// dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);String driverClassName = dataSourceMap.get("driver").toString();String url = dataSourceMap.get("url").toString();String username = dataSourceMap.get("username").toString();String password = dataSourceMap.get("password").toString();// DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)// .username(username).password(password).type(dataSourceType);// DataSource dataSource = factory.build();DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);dataSource.setInitialSize(1);dataSource.setMaxActive(1000);dataSource.setMinIdle(1);dataSource.setMaxWait(100000);dataSource.setPoolPreparedStatements(true);dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);dataSource.setTimeBetweenEvictionRunsMillis(60000);return dataSource;} catch (Exception e) {e.printStackTrace();}return null;}}
源码
ThreadLocal
set
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}
set 首先获取线程的ThreadLocalMap 不为空就set, 为空就 创建一个map然后set
get
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();}
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}
get方法就是 如果 threadLocalMap 不是空,直接获取其中的值并返回,如果为空,创建一个空的map,然后返回null.
ThreadLocalMap
ThreadLocalMap的源码和HashMap源码类似
使用
把用户登录信息存储到 ThreadLocal中
public class AdminThreadLocal {private static final ThreadLocal<WeakReference<AdminContext>> local = new ThreadLocal<>();public static void set(WeakReference<AdminContext> adminContext) {local.set(adminContext);}public static WeakReference<AdminContext> get() {return local.get();}public static void remove() {local.remove();}}
使用弱引用封装存储对象
WeakReference<AdminContext> wa = new WeakReference<AdminContext>(context);AdminThreadLocal.set(wa);
执行完成销毁
@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {AdminThreadLocal.remove();}
