使用ThreadLocal和InheritableThreadLocal存储和传递线程专用变量

记录:389

场景:使用InheritableThreadLocal和ThreadLocal,实现一个变量var,在线程A执行的多个环节中传递变量var。比如从HttpServletRequest的请求头获取的token值存放到ThreadLocal,那么在Controller层、Service层、Dao层以及相关环节中均可以从ThreadLocal获取token值。

版本:JDK 1.8,SpringBoot 2.6.3

1.实现ThreadLocal工具类

1.1工具类

public class ThreadLocalTool {
  private static InheritableThreadLocal<Map<String, Object>> reqMap = new InheritableThreadLocal<Map<String, Object>>() {
    // initialValue是ThreadLocal初始化方法
    protected Map<String, Object> initialValue() {
        return new HashMap<>();
    }
    // parentValue值存入childValue一并返回
    protected Map<String, Object> childValue(Map<String, Object> parentValue) {
        Map<String, Object> childValue = this.initialValue();
        childValue.putAll(parentValue);
        return childValue;
    }
  };
  // 获取当前线程值,reqMap.get()是ThreadLocal的get方法
  public static Map<String, Object> getCurrentValue() {
      return reqMap.get();
  }
  // 获取当前线程指定key的值
  public static Object getCurrentValueByKey(String key) {
      return reqMap.get().get(key);
  }
  // 设置当前线程值
  public static void setCurrentValue(String key, Object obj) {
      reqMap.get().put(key, obj);
  }
  // 清空当前线程值
  public static void clearValue() {
      reqMap.get().clear();
  }
}

1.2ThreadLocal使用ThreadLocalMap为每个线程维护一份数据

(1)初始化创建对象InheritableThreadLocal,此对象继承ThreadLocal。

(2)在InheritableThreadLocal的initialValue方法中,传入给ThreadLocal一个已初始的对象,自定义变量就存储在这个对象中。

protected Map<String, Object> initialValue() {
    return new HashMap<>();
}

(3)在ThreadLocal中的setInitialValue方法中,使用ThreadLocalMap为每个线程维护一份数据。

ThreadLocalMap的key值是当前线程,value是传入的对象。

类:java.lang.ThreadLocal

扫描二维码关注公众号,回复: 14784870 查看本文章

方法:

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);
}

1.3存储变量逻辑

(1)工具类方法,ThreadLocalTool.setCurrentValue("中国", "china")。

(2)底层调用ThreadLocal的get方法获取当前线程变量Map<String, Object>对象。

自定义代码入口:

public static void setCurrentValue(String key, Object obj) {
    reqMap.get().put(key, obj);
}

底层ThreadLocal代码入口:

类:java.lang.ThreadLocal

方法: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();
}

解析:为根据当前线程获取为该线程存储的变量。

  1. 调用Map<String, Object>的put(key, obj)存入值。

1.4读取变量逻辑

(1)工具类方法,ThreadLocalTool.getCurrentValueByKey("中国")。

(2)底层调用ThreadLocal的get方法获取当前线程变量Map<String, Object>对象。

自定义代码入口:

public static Object getCurrentValueByKey(String key) {
  return reqMap.get().get(key);
}

底层ThreadLocal代码入口:

类:java.lang.ThreadLocal

方法: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();
}

解析:为根据当前线程获取为该线程存储的变量。

(3)调用Map<String, Object>的get(key)存入值。

2.关于ThreadLocal本质

ThreadLocal为每个线程存储一份变量的核心,就是ThreadLocal内部维护了ThreadLocalMap。ThreadLocalMap类是一个(Key,Value)键值对类,以每个线程对象为key,以传入给为ThreadLocal的对象Value。达到一个线程对应一个变量效果。

3.验证ThreadLocal示例

3.1工具类ThreadLocalTool

public class ThreadLocalTool {
  private static InheritableThreadLocal<Map<String, Object>> reqMap = new InheritableThreadLocal<Map<String, Object>>() {
    // initialValue是ThreadLocal初始化方法
    protected Map<String, Object> initialValue() {
        return new HashMap<>();
    }
    // parentValue值存入childValue一并返回
    protected Map<String, Object> childValue(Map<String, Object> parentValue) {
        Map<String, Object> childValue = this.initialValue();
        childValue.putAll(parentValue);
        return childValue;
    }
  };
  // 获取当前线程值,reqMap.get()是ThreadLocal的get方法
  public static Map<String, Object> getCurrentValue() {
      return reqMap.get();
  }
  // 获取当前线程指定key的值
  public static Object getCurrentValueByKey(String key) {
      return reqMap.get().get(key);
  }
  // 设置当前线程值
  public static void setCurrentValue(String key, Object obj) {
      reqMap.get().put(key, obj);
  }
  // 清空当前线程值
  public static void clearValue() {
      reqMap.get().clear();
  }
}

3.2线程HangzhouRunnable

public class HangzhouRunnable implements Runnable {
  private long millis;
  public HangzhouRunnable(long millis) {
      this.millis = millis;
  }
  @Override
  public void run() {
    System.out.println("HangzhouThread加入数据.");
    ThreadLocalTool.setCurrentValue("杭州", "hangzhou");
    ThreadLocalTool.setCurrentValue("杭州-西湖", "hangzhou-xihu");
    this.sleep();
    System.out.println("HangzhouThread遍历数据(休息3秒后),线程名称: "+Thread.currentThread().getName());
  
    Map<String, Object> map = ThreadLocalTool.getCurrentValue();
    map.forEach((key, value) -> {
        System.out.println("key = " + key + ",value = " + value);
    });
  }
  public void sleep() {
    try {
        Thread.sleep(this.millis);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
  }
}

3.3线程SuzhouRunnable

public class SuzhouRunnable implements Runnable {
  private long millis;
  public SuzhouRunnable(long millis){
    this.millis=millis;
  }
  @Override
  public void run() {
    System.out.println("SuzhouThread加入数据.");
    ThreadLocalTool.setCurrentValue("苏州","suzhou");
    ThreadLocalTool.setCurrentValue("苏州-昆山","suzhou-kunshan");
    this.sleep();
    System.out.println("SuzhouThread遍历数据(休息2秒后),线程名称: "+Thread.currentThread().getName());
    Map<String, Object> map= ThreadLocalTool.getCurrentValue();
    map.forEach((key,value)->{
        System.out.println("key = " + key + ",value = " + value);
    });
  }
  public void sleep(){
    try {
        Thread.sleep(this.millis);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
  }
}

3.4主线类

public class MainDemo {
  public static void main(String[] args) {
    System.out.println("MainDemo加入数据.");
    ThreadLocalTool.setCurrentValue("中国", "china");
    Thread hangzhouThread = new Thread(new HangzhouRunnable(3000));
    hangzhouThread.start();
    Thread suzhouThread = new Thread(new SuzhouRunnable(2000));
    suzhouThread.start();
    sleep(5000);
    Map<String, Object> main = ThreadLocalTool.getCurrentValue();
    System.out.println("MainDemo遍历数据(休息5秒后),线程名称: "+Thread.currentThread().getName());
    main.forEach((key, value) -> {
        System.out.println("key = " + key + ",value = " + value);
    });
  }
  
  public static void sleep(long millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
  }
}

3.5测试结论

(1)主线程变量可以传递到子线程。

(2)子线程只能读取子线程内容。

(3)执行后打印

MainDemo加入数据.
HangzhouThread加入数据.
SuzhouThread加入数据.
SuzhouThread遍历数据(休息2秒后),线程名称: Thread-1
key = 苏州-昆山,value = suzhou-kunshan
key = 中国,value = china
key = 苏州,value = suzhou
HangzhouThread遍历数据(休息3秒后),线程名称: Thread-0
key = 中国,value = china
key = 杭州,value = hangzhou
key = 杭州-西湖,value = hangzhou-xihu
MainDemo遍历数据(休息5秒后),线程名称: main
key = 中国,value = china

以上,感谢。

2023年3月23日

猜你喜欢

转载自blog.csdn.net/zhangbeizhen18/article/details/129740621