ThreadLocal的使用场景和使用方式| 8月更文挑战

110 阅读2分钟

典型场景1:每个线程都需要独享的对象

每个Thread内有自己的实例副本,不共享;

举例:SimpleDateFormat。(当多个线程共用这样一个SimpleDateFormat,但是这个类是不安全的)

  • 2个线程分别用自己的SimpleDateFormat,这没问题;
  • 后来延伸出10个,那就有10个线程和10个SimpleDateFormat,这虽然写法不优雅,但勉强可以接受
  • 但是当需求变成了1000,那么必然要用线程池,消耗内存太多;
  • 但是每一个SimpleDateFormat我们都需要创建一遍,那么太耗费new对象了,改成static共用的,所有线程都共用一个simpleDateFormat对象,但这是线程不安全的,容易出现时间一致的情况,在调用的时候,可加锁来解决,但还是不优雅;
  • 用ThreadLocal来解决该问题,给每个线程分配一个simpledateformat,可这个threadlocal是安全的; 代码如下,使用多线程调用dateFormatThreadLocal的get方法即可。 public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

典型场景2:当前用户信息需要被线程内所有方法共享

  • 一个比较繁琐的解决方案是把user作为参数层层传递,从service-1()传到service-2(),以此类推,但是这样做会导致代码冗余且不易维护。

  • 进阶点就是userMap来保存,但是当多线程同时工作时,需要保证线程安全,需要用synchronized,或者 Concurrenthashmap,但无论用什么,都会对性能有所影响 每个线程内需要保存全局变量,可以让不同方法直接使用,避免参数传递的麻烦

  • 用 ThreadLocal 保存一些业务内存(用户权限信息,从用户系统获取到的用户名、userId等)

  • 这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的

  • 在线程生命周期内,都通过这个静态ThreadLocal实例的get()方法取得自己set过的那个对象,避免了将这个对象作为参数传递的麻烦

private static final ThreadLocal<Map<String, Object>> threadLocal = new InheritableThreadLocal<>();

public static void set(String key, Object value) {
    Map<String, Object> map = threadLocal.get();
    if (map == null) {
        map = new HashMap<>();
        threadLocal.set(map);
    }
    map.put(key, value);
}

public static Object get(String key) {
    Map<String, Object> map = threadLocal.get();
    if (map == null) {
        map = new HashMap<>();
        threadLocal.set(map);
    }
    return map.get(key);
}

public static void remove(){
   threadLocal.remove();
}

ThreadLocal使用问题内存泄露

调用remove方法,就会删除对应的Entry对象,可以避免内存泄露,所以使用完ThreadLocal之后,应该调用remove方法。