ThreadLocal 原理解析和netty 的FastThreadLocal

jdk的ThreadLocal解析

threadLocal 主要解决线程安全问题。
加锁解决是让每一个线程有序的访问,并且在访问完之后需要将对象状态数据刷新到主内存中,下一个线程在访问的时候,数据必须从主内存中load,不能使用缓存。
threadLocal 解决的办法主要是为每一个线程都生成一个当前对象的副本,每个线程都更改自己的。这样也就不存在竞争问题,也没有了锁的耗时和等待。但是相应的内存的消耗就变大了。因为本来内存中只有一份数据。现在变成每个线程都有一个数据。线程数越多副本数也越多。
那么threadLocal 是如何实现每个线程都有一个数据副本?以及threadLocal的内存泄露风险。
其实很简单。在Java 中Thread 类中有一个属性是 ThreadLocalMap,此类是ThreadLocal 中的一个内部类。主要就是这个map,因为每一个线程都有一个ThreadLocalMap,Java 就将我们要缓存在threadLocal中的数据放在了ThreadLocalMap,但是在放入的时候都是单独new出来的。ThreadLocalMap 的value是new出来的对象,key就是threadLocal对象自己。
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();
    }

先获取当前线程,在获取当前线程的ThreadLocalMap,就是我们上面分析过的,每一个Thread都有一个ThreadLocalMap 属性。然后通过将当前ThreadLocal实例也就是此方法中的this,这里的this从当map中的key,而我们想要多个线程副本的对象就是map中的value。
总结一下:不同的线程对象就有不同的ThreadLocalMap,不同的ThreadLocalMap 保存着当前线程的副本。副本存在map中的value。
但是ThreadLocal对象只有一个从当key可以获取当前线程在map中的副本。结构类似于下表
ThreadLocal 原理解析和netty 的FastThreadLocal_第1张图片
thread1中有属性ThreadLocalMap1,ThreadLocalMap1 针对ThreadLocal1保存的数据是ThreadLocal1,obj11。这样的键值对。obj11就是在Thread1中的副本数据。
ThreadLocal 内存泄漏风险
由于ThreadLocalMap是Thread中的属性所以生命过程和当前线程强绑定在一起,只要thread不消亡,则threadLocalMap的也就一直在,如果ThreadLocalMap 一直在,那么这个map里面保存的数据也就一直在。如果ThreadLocal 在外部没有强引用,比如定义在方法里,每次都new一个ThreadLocal,每次都是新的ThreadLocalMap会不停的增加键值对。而且方法执行完之后ThreadLocal就会被释放,这样ThreadLocalMap中的数据由于没有key可以访问到了,又不能释放就会一直占据着内存,但你又没有办法访问到。造成内存泄露。
jdk针对这个风险,采取的对策就是将threadLocalMap 中entry定义为了针对ThreadLocal的弱引用。

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

这样只要ThreadLocal被回收了,那么相关entry的key也会变成null。而ThreadLocal在get和set的时候都会处理掉key为null的键值对。这样就能将相关value进行回收。
netty 中FastThreadLocal
FastThreadLocal 原理和ThreadLocal 原理差不多。区别主要是保存数据和获取数据方式上不同。要想使用 FastThreadLocal 需要使用netty提供的Thread类 FastThreadLocalThread 此类继承于jdk的Thread,有一个私有属性 InternalThreadLocalMap,这个属性就相当于上面提到的ThreadLocalMap,用来保存各个线程中的副本数据。不过它保存数据不是通过map来进行的而是通过一个object 数组。在其父类 UnpaddedInternalThreadLocalMap 中定义 Object[] indexedVariables;。和上面类比分析各个线程保存数据的容器有了,那如何获取数据。在new FastThreadLocal的时候看构造方法

public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }

每一个FastThreadLocal都有一个 index下标。而这个index本质是 AtomicInteger,每次定义一个FastThreadLocal 就会自增1.
直接看 InternalThreadLocalMap 的 nextVariableIndex 方法

public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

static final AtomicInteger nextIndex = new AtomicInteger();
看到这里应该就明白了,每个线程通过不同的InternalThreadLocalMap保存数据,每一个保存的数据通过index做下标来获取和保存。index是一个静态变量并且是线程安全的。
和jdk的ThreadLocal对比
jdk的ThreadLocal 采用 ThreadLocalMap 保存数据。netty 的 Fast Thread Local采用 InternalThreadLocalMap 中的Object [] 数组保存数据。
在get和set的时候。jdk 采用当前 ThreadLocal 最为key进行get和set。而netty通过FastThreadLocal 的index属性,将index作为下标直接在Object[]数组中进行get和set。

你可能感兴趣的:(jdk)