*ThreadLocal 详解

ThreadLocal是什么

ThreadLocal可以理解为线程局部变量,当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,ThreadLocal保证了各个线程的数据互不干扰。

ThreadLocal原理

*ThreadLocal 详解_第1张图片

 

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

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

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

 ThreadLocalMap

每个线程中都有一个ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行get方法中,是从当前线程的threadLocals变量获取。

也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象。通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。

ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了

*ThreadLocal 详解_第2张图片

Entry

static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

 hash冲突

每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647

在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下:

  • 如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;
  • 位置i已经有Entry对象,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
  • 位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;

这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置
可以发现,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为。

 

 内存泄露

当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

解决:

在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。

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

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

为什么使用弱引用

考虑两种情况:

  • key 使用强引用:
    • 引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用
    • 引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。
    • value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除

ThreadLocal和synchronized 区别:

synchronized:以时间换空间,一个变量,让不同线程排队访问;

ThreadLocal:空间换时间,多个变量,多个线程之间同时访问不受影响。

 

ThreadLocal 无法解决共享对象的更新问题:

ThreadLocal建议使用static修饰,这个变量是针对一个线程内所有操作共享的,所以设置成静态的,所有此类实例共享此静态变量,也就是说在类加载时分配一块存储空间,所有此类的对象都可以操作这个变量。

ThreadLocal 应用场景

  • 保存线程上下文信息的,在任意地方可以取
  • 线程安全的,避免某些情况下需要考虑线程安全必须同步带来的性能损失。

主要用于一些connection,session资源的管理,比如SqlSession

sqlsession管理

spring 事务管理 TransactionSynchronizationManager。用ThreadLocal存储Connection,从而各个DAO可以获取同一个Connection,可以进行事务回滚,提交等操作。

 

ThreadLocal 和 FastThreadLocal

有测试说FastThreadLocal是ThreadLocal 3倍的速率;

FastThreadLocal是Netty提供的,在池化内存分配的地方都有涉及到。

FastThreadLocal的使用

  • FastThreadLocal用法上兼容ThreadLocal,

  • 使用FastThreadLocal居然不用像ThreadLocal那样先try ………………… 之后finally进行threadLocal对象.remove();

由于构造FastThreadLocalThread的时候,通过FastThreadLocalRunnable对Runnable对象进行了包装:

*ThreadLocal 详解_第3张图片

FastThreadLocalRunnable在执行完之后都会调用FastThreadLocal.removeAll();

FastThreadLocal并不是什么情况都快,FastThreadLocalThread线程才会快,如果是普通线程还更慢。

  • FastThreadLocal操作元素的时候,使用常量下标在数组中进行定位元素来替代ThreadLocal通过哈希和哈希表,这个改动特别在频繁使用的时候,效果更加显著!

  • 想要利用上面的特征,线程必须是FastThreadLocalThread或者其子类,默认DefaultThreadFactory都是使用FastThreadLocalThread的

  • 只用在FastThreadLocalThread或者子类的线程使用FastThreadLocal才会更快,因为FastThreadLocalThread 定义了属性threadLocalMap类型是InternalThreadLocalMap。如果普通线程会借助ThreadLocal。

你可能感兴趣的:(Java)