美文网首页
线程本地存储模式

线程本地存储模式

作者: woshishui1243 | 来源:发表于2019-12-02 17:38 被阅读0次

    多个线程同时读写同一共享变量存在并发问题。要解决这个问题,我们可以突破的写,没有写操作自然没有并发问题了。其实还可以突破共享变量,没有共享变量也不会有并发问题,没有共享,就没有伤害

    那如何避免共享呢?每个线程都拥有自己的变量,彼此之间不共享,也就没有并发问题了。

    通过局部变量可以做到避免共享,Java 语言提供的线程本地存储(ThreadLocal)也能够做到。下面我们先看看 ThreadLocal 到底该如何使用。

    ThreadLocal 的使用方法

    SimpleDateFormat 不是线程安全的,那如果需要在并发场景下使用它,你该怎么办呢?
    其实有一个办法就是用 ThreadLocal 来解决,下面的示例代码就是 ThreadLocal 解决方案的具体实现:

    static class SafeDateFormat {
      // 定义 ThreadLocal 变量
      static final ThreadLocal<DateFormat>
      tl=ThreadLocal.withInitial(
        ()-> new SimpleDateFormat(
          "yyyy-MM-dd HH:mm:ss"));
          
      static DateFormat get(){
        return tl.get();
      }
    }
    // 不同线程执行下面代码
    // 返回的 df 是不同的
    DateFormat df =
      SafeDateFormat.get();
    

    ThreadLocal 的工作原理

    ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。


    Thread 持有 ThreadLocalMap 的示意图
    class Thread {
      // 内部持有 ThreadLocalMap
      ThreadLocal.ThreadLocalMap 
        threadLocals;
    }
    class ThreadLocal<T>{
      public T get() {
        // 首先获取线程持有的
        //ThreadLocalMap
        ThreadLocalMap map =
          Thread.currentThread()
            .threadLocals;
        // 在 ThreadLocalMap 中
        // 查找变量
        Entry e = 
          map.getEntry(this);
        return e.value;  
      }
      static class ThreadLocalMap{
        // 内部是数组而不是 Map
        Entry[] table;
        // 根据 ThreadLocal 查找 Entry
        Entry getEntry(ThreadLocal key){
          // 省略查找逻辑
        }
        //Entry 定义
        static class Entry extends
        WeakReference<ThreadLocal>{
          Object value;
        }
      }
    }
    

    ThreadLocal 与内存泄露
    在线程池中使用 ThreadLocal 为什么可能导致内存泄露呢?
    因为ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

    其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。

    但是这些被动的预防措施并不能保证不会内存泄漏:

    • 使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏。
    • 分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。

    ThreadLocal 最佳实践

    每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
    在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。

    相关文章

      网友评论

          本文标题:线程本地存储模式

          本文链接:https://www.haomeiwen.com/subject/qzufgctx.html