Netty的FastThreadLocal

作者 chauncy 日期 2016-12-09
Netty的FastThreadLocal

Summary:
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

JDK本身的ThreadLocal

ThreadLocal 常用的就是用于线程上下文传递信息,常用的方法为get和set,get方法从ThreadLocal中取出数据,与之对应的set将数据存储到ThreadLocal
set方法代码:

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

主要是通过当前thread,获得到ThreadLocalMap对象,如果对象之前存在那么将其添加,map.set 这个调用如果有冲突那么在后面追加,如果不存在将其创建。ThreadLocalMap 本身也是ThreadLocal 的一个静态内部类,该类中主要包括一个Entry,Entry类继承了WeakReference,这也是为了在一定程度上防止内存泄漏。

具体的关系如图:

每一个线程都会关联一个ThreadLocalMap 实例,ThreadLocalMap里面是一个Entry[] table;用于存放具体的值, Entry是扩展了WeakReference,提供了一个存储value的地方,一个线程可以对应多个ThreadLocal实例,一个ThreadLocal也可以对应多个Thread。如果一个线程里面有多个ThreadLocal对象,这些ThreadLocal和线程相关的值被存放在于线程关联的ThreadLocalMap中

ThreadLocalMap 的set方法代码:
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

for (Entry e = tab[i];
     e != null;
     e = tab[i = nextIndex(i, len)]) {
    ThreadLocal<?> k = e.get();

    if (k == key) {
        e.value = value;
        return;
    }

    if (k == null) {
        replaceStaleEntry(key, value, i);
        return;
    }
}

tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();

每个ThreadLocal实例都有一个唯一的threadLocalHashCode初始值(也有可能会产生重复的冲突)。上面首先根据threadLocalHashCode值计算出i,有下面两种情况会进入for循环:

由于threadLocalHashCode&(len-1)的值对应的槽有内容,因此满足tab[i]!=null条件,进入for循环,如果满足条件且当前key不是当前threadlocal只能说明hash冲突了。

ThreadLocal实例之前被设值过,因此足tab[i]!=null条件,进入for循环。

进入for循环会遍历tab数组,如果遇到以当前threadLocal为key的槽,有则直接将值替换;如果找到了一个已经被回收的ThreadLocal对应的槽,也就是当key==null的时候表示之前的threadlocal已经被回收了,但是value值还存在,这也是ThreadLocal内存泄露的地方。碰到这种情况,则会引发替换这个位置的动作,如果上面两种情况都没发生,则新创建一个Entry对象放入槽中。

ThreadLocal的性能,一个线程对应多个ThreadLocal实例的场景中,在没有命中的情况下基本上一次hash就可以找到位置,如果发生没有命中的情况,则会引发性能会急剧下降,本身是O(1),结果变成了O(n)当在读写操作频繁的场景,这点导致性能的后滞。

Netty FastThreadLocal:

FastThreadLocal提供的接口和传统的ThreadLocal一致,主要是set和get方法,用法也一致。性能的提升在于FastThreadLocal的值是存储在InternalThreadLocalMap

FastThreadLocal的构造方法:

public FastThreadLocal() {
    index = 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;
    }

nextIndex 则是这样的:
static final AtomicInteger nextIndex = new AtomicInteger();
使用全局静态的AtomicInteger类型的对象生产索引,将去InternalThreadLocalMap里面Object[] indexedVariables这个数组里面取出对象

这个数组用来存储跟同一个线程关联的多个FastThreadLocal的值,由于FastThreadLocal对应indexedVariables的索引是确定的,因此在读写的时候将会发生随机存取,非常快。

另外这里有一个问题,nextIndex是静态唯一的,而indexedVariables数组是实例对象的,因此随着FastThreadLocal数量的递增,这也就是用空间换取时间。