• 什么是ThreadLocal?如何正确使用ThreadLocal?


    ThreadLocal线程本地存储

    多个线程同时读写同一个共享变量会造成并发问题,一种解决方案就是避免变量共享。我们可以使用线程封闭技术,即使用局部变量,每个线程都有各自的调用栈,局部变量就存在栈帧中,不会与其他线程共享。我们还可以使用线程本地存储ThreadLocal

    如何使用 ThreadLocal

    下面这段代码会为每个线程分配一个唯一的线程Id,同一个线程每次调用 get() 获得的 Id 是一样的,不同的线程调用 get() 获得的 Id 是不一样的。

    static class ThreadId {
      static final AtomicLong nextId = new AtomicLong(0);
      //定义ThreadLocal变量
      static final ThreadLocal<Long> tl=
        ThreadLocal.withInitial(()->nextId.getAndIncrement());
      //此方法会为每个线程分配一个唯一的Id
      static long get(){
        return tl.get();
      }
    }
    

    ThreadLocal

    ThreadLocal 中除了构造方法还有 4 个公共的方法:

    1. get():返回此线程局部变量当前副本中的值

    2. remove():移除此线程局部变量当前副本中的值

    3. set(T value):将线程局部变量当前副本中的值设置为指定值

    4. withInitial(Supplier<? extends S> supplier):返回此线程局部变量当前副本中的初始值

    ThreadLocal 的工作原理

    ThreadLocal 要实现的目标是:不同的线程对应不同的变量,很自然地可以想到创建一个 Map,其中 Key 是线程,Value 是线程对应的值。那么可以让 ThreadLocal 持有一个这样的 map,并提供对应的方法,就像下面这样:

    class MyThreadLocal<T> {
      Map<Thread, T> locals = 
        new ConcurrentHashMap<>();
      //获取线程变量  
      T get() {
        return locals.get(
          Thread.currentThread());
      }
      //设置线程变量
      void set(T t) {
        locals.put(
          Thread.currentThread(), t);
      }
    }
    

    这样设计会产生内存泄露的问题。ThreadLocal 持有 Map 的引用,Map 持有 Thread 对象的引用。这就意味着,只要 ThreadLocal 对象存在,那么 Map 中的 Thread 对象就不会被释放。ThreadLocal 对象的生命周期往往比线程要长得多,当长生命周期的对象持有短生命周期对象的引用,就会造成内存泄露问题

    在 Java 的设计中,ThreadLocal 持有的 Map 被命名为 ThreadLocalMap。ThreadLocalMap 并不是由 ThreadLocal 持有,而是由 Thread 持有。ThreadLocal 作为一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在 Thread 里面。如下面的代码所示:

    public
    class Thread implements Runnable {
      // Thread 内部持有 ThreadLocalMap
      ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    public class ThreadLocal<T> {
      
    	public T get() {
        // 1. 获取线程持有的 ThreadLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        
        // 2. 在Map中查找变量
        if (map != null) {
          ThreadLocalMap.Entry e = map.getEntry(this);
          if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
          }
        }
        return setInitialValue();
      }
      
      static class ThreadLocalMap{
    		
        // Entry定义,是一个弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
          /** The value associated with this ThreadLocal. */
          Object value;
          Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
          }
        }
        
        // 内部是数组而不是Map
        private Entry[] table;
        // 根据ThreadLocal查找Entry
        Entry getEntry(ThreadLocal key){
          //省略查找逻辑
          ...
        }
      }
    }
    

    ThreadLocal各引用之间的关系

    理解 ThreadLocal 的原理,要结合上面这张图和代码:

    1. 当前线程线程持有 ThreadLocalMap,ThreadLocalMap 持有 Entry;
    2. Entry 的 Key 是一个 ThreadLocal 实例,并且是一个弱引用,value 是我们要存储的值;

    Java 的实现中 Thread 持有 ThreadLocalMap,ThreadLocalMap 中的 Entry 对 ThreadLocal 的引用是弱引用,所以只要 Thread 对象可以被回收,那么 ThreadLocalMap 就能被回收。

    ThreadLocal 与内存泄露

    在线程池中使用 ThreadLocal 任然可能会出现内存泄露。

    因为线程池中线程的存活时间太长了,往往是和应用程序同生共死的。这就意味着 Thread 持有的 ThreadLocalMap 一直不会被回收,ThreadLocalMap 中 Entry 对 ThreadLocal 的引用是弱引用,所以只要 ThreadLocal 的生命周期结束是可以被回收的。但是 Entry 对 Value 是强引用,即使 value 的生命周期结束也无法被回收,这就造成了内存泄露

    在线程池中如何正确使用 ThreadLocal?

    那在线程池中,我们该如何正确使用 ThreadLocal 呢?既然 JVM 无法帮我们释放对 value 的引用,那么我们就使用 try{}finally{} 手动释放资源:

    ExecutorService es;
    ThreadLocal tl;
    es.execute(()->{
      //ThreadLocal增加变量
      tl.set(obj);
      try {
        // 省略业务逻辑代码
      }finally {
        //手动清理ThreadLocal 
        tl.remove();
      }
    });
    

    相关文章

    面试再问ThreadLocal,别说你不会

    并发容器之ThreadLocal

    30 | 线程本地存储模式:没有共享,就没有伤害

  • 相关阅读:
    POJ2104 K-th Number Range Tree
    BZOJ 3390: [Usaco2004 Dec]Bad Cowtractors牛的报复(最大生成树)
    BZOJ 3391: [Usaco2004 Dec]Tree Cutting网络破坏(搜索)
    BZOJ 3412: [Usaco2009 Dec]Music Notes乐谱(离线处理)
    BZOJ 3410: [Usaco2009 Dec]Selfish Grazing 自私的食草者(贪心)
    BZOJ 3403: [Usaco2009 Open]Cow Line 直线上的牛(模拟)
    BZOJ 3402: [Usaco2009 Open]Hide and Seek 捉迷藏(最短路)
    BZOJ 3479: [Usaco2014 Mar]Watering the Fields(最小生成树)
    BZOJ 3432: [Usaco2014 Jan]Cross Country Skiing (二分+染色法)
    BZOJ 3299: [USACO2011 Open]Corn Maze玉米迷宫(BFS)
  • 原文地址:https://www.cnblogs.com/shuiyj/p/13185081.html
Copyright © 2020-2023  润新知