线程不隔离 变量获取数据窜了

  1. /**
  2. * @author meikb
  3. * @desc 线程不隔离 变量获取数据窜了
  4. * 通过使用ThreadLocal让线程变量实现隔离
  5. * @date 2020-05-19 19:06
  6. */
  7. public class ThreadLocal1 {
  8. private String context;
  9. ThreadLocal<String> local = new ThreadLocal<String>();
  10. public String getContext() {
  11. return local.get();
  12. // return context;
  13. }
  14. public void setContext(String context) {
  15. local.set(context);
  16. // this.context = context;
  17. }
  18. public static void main(String[] args) {
  19. fun1();
  20. }
  21. public static void fun1(){
  22. ThreadLocal1 tt = new ThreadLocal1();
  23. for (int i = 0; i < 5; i++) {
  24. Thread t = new Thread(new Runnable() {
  25. @Override
  26. public void run() {
  27. tt.setContext(Thread.currentThread().getName() + "data");
  28. try {
  29. Thread.sleep(100);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println(Thread.currentThread().getName() + "-------->" + tt.getContext());
  34. }
  35. });
  36. t.setName("线程" + i);
  37. t.start();
  38. }
  39. }
  40. }

使用synchronzied 也可以解决

  1. public class ThreadLocal2 {
  2. private String context;
  3. public String getContext() {
  4. return context;
  5. }
  6. public void setContext(String context) {
  7. this.context = context;
  8. }
  9. public static void main(String[] args) {
  10. fun1();
  11. }
  12. public static void fun1(){
  13. ThreadLocal2 tt = new ThreadLocal2();
  14. for (int i = 0; i < 5; i++) {
  15. Thread t = new Thread(new Runnable() {
  16. @Override
  17. public void run() {
  18. synchronized (ThreadLocal2.class){
  19. tt.setContext(Thread.currentThread().getName() + "data");
  20. try {
  21. Thread.sleep(100);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println(Thread.currentThread().getName() + "-------->" + tt.getContext());
  26. }
  27. }
  28. });
  29. t.setName("线程" + i);
  30. t.start();
  31. }
  32. }
  33. }

使用synchronized 可以解决线程变量独立问题,但是实际上是时间换取空间,每个线程顺序执行,所以效率比较低下。

使用场景

  1. 再jdbc的事务回滚场景中,Service要调用dao的方法,在service 加上事务,但是dao要和Service的connection

一致才能回滚,这个可以把connecttion存放在threadlocal中,这样事务肯定能回滚

  1. 多数据源场景下,如果多数据源的变量存储再普通string对象中,就可能再多线程的情况下,一个线程获取到另一个线程的数据源,导致数据源错乱。

多数据源实战

多数据源使用spring提供的一个非要好用的一个类 AbstractRoutingDataSource

  1. private Map<Object, Object> targetDataSources; //存储的是从数据源的map 传入不同的key
  2. private Object defaultTargetDataSource; //默认数据源
  3. //通过这个方法 设置不同的key 就可以切换不同的数据源
  4. protected Object resolveSpecifiedLookupKey(Object lookupKey) {
  5. return lookupKey;
  6. }

1.定义全局变量类

  1. public class DynamicDataSourceContextHolder {
  2. private Logger logger = Logger.getLogger(DynamicDataSourceContextHolder.class);
  3. //存放当前线程使用的数据源类型信息 防止高并发 线程获取数据源错乱
  4. private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
  5. //存放数据源id
  6. public static List<String> dataSourceIds = new ArrayList<String>();
  7. //设置数据源
  8. public static void setDataSourceType(String dataSourceType) {
  9. contextHolder.set(dataSourceType);
  10. }
  11. //获取数据源
  12. public static String getDataSourceType() {
  13. return contextHolder.get();
  14. }
  15. //清除数据源
  16. public static void clearDataSourceType() {
  17. contextHolder.remove();
  18. }
  19. //判断当前数据源是否存在
  20. public static boolean isContainsDataSource(String dataSourceId) {
  21. return dataSourceIds.contains(dataSourceId);
  22. }
  23. }

2.继承 AbstractRoutingDataSource类 使用 通过Threadlocal get放当前线程的key

  1. public class DynamicDataSource extends AbstractRoutingDataSource {
  2. @Override
  3. protected Object determineCurrentLookupKey() {
  4. return DynamicDataSourceContextHolder.getDataSourceType();
  5. }
  6. }

3.定义注解切面,不同的注解使用不同的key

  1. @Aspect
  2. @Order(-1)//保证在@Transactional之前执行
  3. @Component
  4. public class DynamicDattaSourceAspect {
  5. private Logger logger = Logger.getLogger(DynamicDattaSourceAspect.class);
  6. //改变数据源
  7. @Before("@annotation(targetDataSource)")
  8. public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
  9. String dbid = targetDataSource.name();
  10. if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) {
  11. //joinPoint.getSignature() :获取连接点的方法签名对象
  12. logger.error("数据源 " + dbid + " 不存在使用默认的数据源 -> " + joinPoint.getSignature());
  13. } else {
  14. logger.debug("使用数据源:" + dbid);
  15. DynamicDataSourceContextHolder.setDataSourceType(dbid);
  16. }
  17. }
  18. @After("@annotation(targetDataSource)")
  19. public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
  20. logger.debug("清除数据源 " + targetDataSource.name() + " !");
  21. DynamicDataSourceContextHolder.clearDataSourceType();
  22. }
  23. }

4.开始的时候注册默认数据源和 数据源map

  1. public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
  2. private Logger logger = Logger.getLogger(DynamicDataSourceRegister.class);
  3. //指定默认数据源(springboot2.0默认数据源是hikari如何想使用其他数据源可以自己配置)
  4. private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
  5. //默认数据源
  6. private DataSource defaultDataSource;
  7. //用户自定义数据源
  8. private Map<String, DataSource> slaveDataSources = new HashMap<>();
  9. @Override
  10. public void setEnvironment(Environment environment) {
  11. initDefaultDataSource(environment);
  12. initslaveDataSources(environment);
  13. }
  14. private void initDefaultDataSource(Environment env) {
  15. // 读取主数据源
  16. Map<String, Object> dsMap = new HashMap<>();
  17. dsMap.put("driver", env.getProperty("spring.datasource.driver"));
  18. dsMap.put("url", env.getProperty("spring.datasource.url"));
  19. dsMap.put("username", env.getProperty("spring.datasource.username"));
  20. dsMap.put("password", env.getProperty("spring.datasource.password"));
  21. defaultDataSource = buildDataSource(dsMap);
  22. }
  23. private void initslaveDataSources(Environment env) {
  24. // 读取配置文件获取更多数据源
  25. String dsPrefixs = env.getProperty("slave.datasource.names");
  26. for (String dsPrefix : dsPrefixs.split(",")) {
  27. // 多个数据源
  28. Map<String, Object> dsMap = new HashMap<>();
  29. dsMap.put("driver", env.getProperty("spring.datasource." + dsPrefix + ".driver"));
  30. dsMap.put("url", env.getProperty("spring.datasource." + dsPrefix + ".url"));
  31. dsMap.put("username", env.getProperty("spring.datasource." + dsPrefix + ".username"));
  32. dsMap.put("password", env.getProperty("spring.datasource." + dsPrefix + ".password"));
  33. DataSource ds = buildDataSource(dsMap);
  34. slaveDataSources.put(dsPrefix, ds);
  35. }
  36. }
  37. //把DynamicDataSource 注入到spring 容器,并初始化属性,也可以在继承AbstractRoutingDataSource
  38. //初始化父类的targetDataSources 和 defaultTargetDataSource 参数
  39. @Override
  40. public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
  41. Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
  42. //添加默认数据源
  43. targetDataSources.put("dataSource", this.defaultDataSource);
  44. DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
  45. //添加其他数据源
  46. targetDataSources.putAll(slaveDataSources);
  47. for (String key : slaveDataSources.keySet()) {
  48. DynamicDataSourceContextHolder.dataSourceIds.add(key);
  49. }
  50. //创建DynamicDataSource
  51. GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
  52. beanDefinition.setBeanClass(DynamicDataSource.class);
  53. beanDefinition.setSynthetic(true);
  54. MutablePropertyValues mpv = beanDefinition.getPropertyValues();
  55. mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
  56. mpv.addPropertyValue("targetDataSources", targetDataSources);
  57. //注册 - BeanDefinitionRegistry
  58. beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
  59. logger.info("Dynamic DataSource Registry");
  60. }
  61. public DataSource buildDataSource(Map<String, Object> dataSourceMap) {
  62. try {
  63. Object type = dataSourceMap.get("type");
  64. if (type == null) {
  65. type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
  66. }
  67. // Class<? extends DataSource> dataSourceType;
  68. // dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
  69. String driverClassName = dataSourceMap.get("driver").toString();
  70. String url = dataSourceMap.get("url").toString();
  71. String username = dataSourceMap.get("username").toString();
  72. String password = dataSourceMap.get("password").toString();
  73. // DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
  74. // .username(username).password(password).type(dataSourceType);
  75. // DataSource dataSource = factory.build();
  76. DruidDataSource dataSource = new DruidDataSource();
  77. dataSource.setDriverClassName(driverClassName);
  78. dataSource.setUrl(url);
  79. dataSource.setUsername(username);
  80. dataSource.setPassword(password);
  81. dataSource.setInitialSize(1);
  82. dataSource.setMaxActive(1000);
  83. dataSource.setMinIdle(1);
  84. dataSource.setMaxWait(100000);
  85. dataSource.setPoolPreparedStatements(true);
  86. dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
  87. dataSource.setTimeBetweenEvictionRunsMillis(60000);
  88. return dataSource;
  89. } catch (Exception e) {
  90. e.printStackTrace();
  91. }
  92. return null;
  93. }
  94. }

源码

ThreadLocal

set

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

set 首先获取线程的ThreadLocalMap 不为空就set, 为空就 创建一个map然后set

get

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }
  1. private T setInitialValue() {
  2. T value = initialValue();
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. return value;
  10. }

get方法就是 如果 threadLocalMap 不是空,直接获取其中的值并返回,如果为空,创建一个空的map,然后返回null.

ThreadLocalMap

ThreadLocalMap的源码和HashMap源码类似

使用

把用户登录信息存储到 ThreadLocal中

  1. public class AdminThreadLocal {
  2. private static final ThreadLocal<WeakReference<AdminContext>> local = new ThreadLocal<>();
  3. public static void set(WeakReference<AdminContext> adminContext) {
  4. local.set(adminContext);
  5. }
  6. public static WeakReference<AdminContext> get() {
  7. return local.get();
  8. }
  9. public static void remove() {
  10. local.remove();
  11. }
  12. }

使用弱引用封装存储对象

  1. WeakReference<AdminContext> wa = new WeakReference<AdminContext>(context);
  2. AdminThreadLocal.set(wa);

执行完成销毁

  1. @Override
  2. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
  3. throws Exception {
  4. AdminThreadLocal.remove();
  5. }