线程不隔离 变量获取数据窜了
/**
* @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() {
@Override
public 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() {
@Override
public 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 传入不同的key
private 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>();
//存放数据源id
public 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 {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
3.定义注解切面,不同的注解使用不同的key
@Aspect
@Order(-1)//保证在@Transactional之前执行
@Component
public 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<>();
@Override
public 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 参数
@Override
public 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);
}
//创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
//注册 - BeanDefinitionRegistry
beanDefinitionRegistry.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);
else
createMap(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);
else
createMap(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);
执行完成销毁
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
AdminThreadLocal.remove();
}