FastThreadLocal

Posted sourmango

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FastThreadLocal相关的知识,希望对你有一定的参考价值。

FastThreadLocal

JDK原生ThreadLocal

在日常并发编程中,锁,CAS和线程局部变量一直是实用的三板斧。Java提供的线程局部不变量就是ThreadLocal。每个线程局部变量都只可以被所属的线程进行读写,优美地规避了线程安全问题。

ThreadLocal的使用也极其简单。(已经会的读者可跳过)

public class ThreadLocalTest {

    //展示的数据类
    public static class Data{
        int data;
        public Data(int data){
            this.data=data;
        }

        @Override
        public String toString() {
            return "Data: "+data;
        }

        public int getData() {
            return data;
        }

        public void setData(int data) {
            this.data = data;
        }
    }

    //线程局部变量
    public static final ThreadLocal<Data> data=new ThreadLocal<Data>(){
        @Override
        protected Data initialValue() {
            return new Data(1);
        }
    };

    public static void main(String[] args) {

        //线程1修改data数值,并模拟调度沉睡一秒
        Runnable runnable1=()->{
            Data data=ThreadLocalTest.data.get();
            data.setData(2);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(ThreadLocalTest.data.get());
        };

        //线程2再次修改data数值,并模拟调度,但只沉睡0.5秒
        Runnable runnable2=()->{
            Data data=ThreadLocalTest.data.get();
            data.setData(7);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(ThreadLocalTest.data.get());
        };

        new Thread(runnable1).start();
        new Thread(runnable2).start();

    }
}

我们可以看到上述线程都只能修改数据在本线程内的数据副本,不会影响到其他线程,避免了脏数据。

JDK ThreadLocal的源码也十分容易理解。已经有许多博客对此进行相关阐述,这里只进行大概讲解。

在每一个Thread对象中对维持着一个ThreadLocalMap,用来存储数据

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap的key是ThreadLocal,value是我们需要存储的值。代码如下:

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {

            Object value;

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

每当我们调用get()方法是,代码首先定位到当前线程的ThreadLocalMap,再把调用get()方法的ThreadLocal对象作为key去ThreadLocalMap中查找值,并返回。代码如下:

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

ThreadLocal不足

第一,内存泄漏
在上一小节的代码中,我们可以看到Entry是一个对于ThreadLocal的弱引用。

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {

            Object value;

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

为什么呢?很简单,假设我们现在需要存储一个值线程局部变量到当前线程专属的局部变量容器中ThreadLocalMap。ThreadLocalMap本质上是一个Entry数组。Entry对象持有一个ThreadLocal的弱引用和一个对Object(value)对象的强引用。如果一个对象仅被弱引用持有,那么JVM将回收该对象。同时,Entry对象还持有一个Object的引用。现在使用完一个ThreadLocal之后,我们可以直接设置

threadLocal=null

因为Entry对象仅仅对ThreadLocal进行弱引用持有,因此JVM会回收ThreadLocal对象,相当于清空了key,方便以后存储。

上面,我们注意到Entry对象持有着value对象的强引用。因此,假设现在ThreadLocalMap仍然被线程持有。观察内存,ThreadLocalMap可能持有大量的key为空的Entry对象,在Entry的value较大时,将造成严重的内存泄漏。

ThreadLocal的第二不足是hash未命中时的问题。ThreadLocal是一个Entr数组。当调用get()方法时,代码首先对ThreadLocal进行hash计算,到数组中进行值的获取和返回。当hash未命中时,那么ThreadLocal将进行一次遍历。

FastThreadLcoal设计

Netty重新设计了ThreadLocal,并命名为FastThreadLocal。FastThreadLocal是Netty对ThreadLocal性能的一次优化。

由于篇幅原因,这里我们将对FastThreadLocal的设计进行简单介绍。详细介绍将放在下一篇。

整个FastThreadLocal体系设计FastThreadLocalThread、FastThreadLocal和InternalThreadLocalMap三个类。其中FastThreadLocalThread是对原生态Thread的一次包装。如同Thre对象都有一个唯一的ThreadLocalMap进行局部变量存储的容器,FastThreadLocalThread则是维护了一个InternalThreadLocalMap容器。上一小节提到,ThreadLocal的hash未命中时的性能问题,FastThreadLocal最主要优化就是每一个局部变量的索引都是确定的。

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

而这个索引则是有一个原生态的AtomicInteger来进行计数的。

未完待续。。。

以上是关于FastThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?

谈谈FastThreadLocal为啥能这么快?吊打 ThreadLocal!

FastThreadLocal

FastThreadLocal

FastThreadLocal源码

吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?