Netty源码解析之ThreadLocal的改进和优化(一):FastThreadLocal的源码和实现原理

前言

接上文《Netty源码解析之线程池的实现(二):创建线程与执行任务》,我们知道NioEventLoopGroup在执行任务时,会调用ThreadPerTaskExecutor的execute方法。ThreadPerTaskExecutor中含有一个线程工厂threadFactory,在其execute方法中,会使用线程工厂threadFactory创建一个新线程。这个threadFactory在NioEventLoopGroup的构造方法中创建,为DefaultThreadFactory实例。

然后,在DefaultThreadFactory的newThread方法中,却有一些不一样的东西。

public Thread newThread(Runnable r) {
        //1、将Runnable任务封装成FastThreadLocalRunnable
        //2、拼接线程名称
        //3、调用同名的newThread方法
        Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
        /*省略与本文内容无关的代码*/
        return t;
    }

    //创建一个线程实例,FastThreadLocalThread
    protected Thread newThread(Runnable r, String name) {
        return new FastThreadLocalThread(threadGroup, r, name);
    }

DefaultThreadFactory的newThread方法中,首先创建的是FastThreadLocalThread实例,而不是我们平常使用的Thread,其次使用FastThreadLocalRunnable.wrap®对需要执行的任务进行包装。Netty为什么要这么做?这是本文的内容。

JDK原生ThreadLocal的原理与缺陷

Netty中的FastThreadLocalThread实际上是配合Netty自定义的FastThreadLocal使用的线程,Netty之所以要自定义一个线程本地存储使用的FastThreadLocal,是因为JDK原生的ThreadLocal存在诸多不足。简而言之,Netty认为ThreadLocal不好,要改进它,这个改进版就是FastThreadLocal。

ThreadLocal的原理概括

关于ThreadLocal的实现原理,网上有很多长篇大论的文章,有些说的云里雾里的。我认为ThreadLocal的原理很简单,简而言之就5句话。

1、ThreadLocal的作用是创建一个线程本地存储的变量,让每个线程都保存一份变量的副本。因此在线程Thread类中就必须要有一个容器用来保存自己的副本,这个容器就是Thread类的threadLocals属性。在我们没有使用它时,它的默认值是null。
Thread源码中的threadLocals属性:

ThreadLocal.ThreadLocalMap threadLocals = null;

2、这个ThreadLocalMap里面包含一个哈希表table,用来存储每个ThreadLocal变量的副本。
3、ThreadLocal里面有个属性threadLocalHashCode,用来保存自己的哈希码,这个哈希码用来和哈希表的长度进行取模计算得到ThreadLocal变量的副本在哈希表table中的位置。
ThreadLocal源码中的threadLocalHashCode 属性:

private final int threadLocalHashCode = nextHashCode();

4、既然每个线程使用哈希表来保存ThreadLocal变量的副本,那么肯定会有哈希冲突的问题。即当两个ThreadLocal变量的threadLocalHashCode经过取模计算后,得到的在哈希表table中的位置相同。ThreadLocal使用线性探测法解决哈希冲突的问题,当某个ThreadLocal变量取模后得到的table的位置上有数据的时候,ThreadLocal采取的是办法是找最近的一个空的位置设置数据。
见ThreadLocal内部类ThreadLocalMap的set方法源码:

/*
* 保存ThreadLocal变量到哈希表中
* key:ThreadLocal实例
* value:ThreadLocal变量
*/
private void set(ThreadLocal<?> key, Object value) {
            //用于保存变量的哈希表
            Entry[] tab = table;
            //哈希表的长度
            int len = tab.length;
            //使用threadLocalHashCode 进行取模
            //得到变量在哈希表中的位置i
            int i = key.threadLocalHashCode & (len-1);
            //在哈希表中从第i个位置开始,寻找空位置插入
            //每次循环,都使用nextIndex(i, len)方法获取下一个位置
            //循环结束的条件是e == null,即在哈希表中找到了空位置
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 //取出哈希表当前位置存储的数据
                ThreadLocal<?> k = e.get();
                //若是Entry已经存在并且key等于传入的key,
                //那么这时候直接给这个Entry赋新的value值
                if (k == key) {
                    e.value = value;
                    return;
                }
                //若是Entry存在,但是key为null,
                //则调用replaceStaleEntry来更换这个key为空的Entry
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            //走到这里,说明在哈希表中找到了空位置i
            //将变量插入
            tab[i] = new Entry(key, value);
            //更新sz值
            int sz = ++size;
            //省略无关代码
        }

5、ThreadLocal使用线性探测法解决哈希冲突的问题,那么在获取某线程的ThreadLocal变量时,使用threadLocalHashCode经过取模计算后,得到的在哈希表table中的位置就不一定准确。这个时候就要拿ThreadLocal实例去比对。
因此在哈希表中,就必须定义一个Entry来既保存ThreadLocal变量,也保存ThreadLocal实例。实际上,Entry使用了弱引用来存储ThreadLocal实例。

见ThreadLocal内部类ThreadLocalMap的Entry类的源码:

//WeakReference为弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
            //ThreadLocal变量的值
            Object value;
            //Entry构造方法,传入ThreadLocal实例k和ThreadLocal变量变量v
            Entry(ThreadLocal<?> k, Object v) {
                //调用弱引用WeakReference的构造方法,存储ThreadLocal实例k
                super(k);
                value = v;
            }
        }

以上是ThreadLocal的实现原理,下面从网上盗个图,描述下ThreadLocal的数据结构。
在这里插入图片描述

题外话:ThreadLocal为什么要用弱引用?

关于ThreadLocal为什么要用弱引用这个问题众说纷纭,我认为在其源码的注释中给出了合理解释。
源码注释:
The entries in this hash map extend WeakReference, using its main ref field as the key (which is always a ThreadLocal object). Note that null keys (i.e. entry.get() == null) mean that the key is no longer referenced, so the entry can be expunged from table. Such entries are referred to as “stale entries” in the code that follows.

翻译过来是:
这个entries在这个哈希映射(指的是ThreadLocalMap)中继承了WeakReference,使用其主ref字段作为key(始终是ThreadLocal对象)。**注意null值的键(即i.e. entry.get() == null)表示key不再被引用,所以关联的entry可以从表中删除。**在下面的代码中,这些entries被称为“过期entries”。

重点看这一句话:注意null值的键(即i.e. entry.get() == null)表示key不再被引用,所以关联的entry可以从表中删除。

也就是说,ThreadLocal在判断哪些entry过期需要回收时,是根据entry.get() == null来判断的。entry.get()返回的就是弱引用的ThreadLocal实例。即当我们在代码中将ThreadLocal实例手动置为null了,但是没有调用ThreadLocal的remove方法将对应的entry从线程中删除。那么如果entry对ThreadLocal实例是强引用,则ThreadLocal实例无法被垃圾回收。这样的话,就没有办法去检查哈希表中哪些entry是过期需要删除的了。

因此entry对ThreadLocal实例设计了弱引用,如果某个ThreadLocal实例被手动置为null,那么弱引用不影响其垃圾回收。当ThreadLocal实例被垃圾回收以后,entry.get()方法通过弱引用来获取它时,返回的结果就是null,这时候就知道entry已经过期需要清理。

详情见ThreadLocal源码中遍历哈希表并清理掉过期的Entry的cleanSomeSlots。

    private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            //转存哈希表
            Entry[] tab = table;
            //哈希表的长度
            int len = tab.length;
            do {
                //循环获取哈希表的下一个位置
                i = nextIndex(i, len);
                //当前位置上的entry
                Entry e = tab[i];
                //如果当前位置上面有entry,但是entry弱引用的
                //ThreadLocal实例已经不存在,则需要将其删除
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

ThreadLocal的局限性

1、使用线性探测方法解决哈希冲突问题性能较低。
2、内存泄漏问题。
在使用ThreadLocal时,如果我们在用完后手动将ThreadLocal实例置为null,并且没有在每个用到其的线程上调用ThreadLocal的remove方法将本线程中的副本删除,则就会导致ThreadLocal实例被垃圾回收了,但是每个线程中的副本一直都是被线程的ThreadLocalMap中的哈希表强引用,导致无法垃圾回收,直到线程对象也被垃圾回收为止。

FastThreadLocal的横空出世

FastThreadLocal解决问题的思路

对于ThreadLocal的缺陷,FastThreadLocal的解决方式是:
1、哈希冲突问题
用数组取代哈希表,并且使用一个全局的AtomicInteger型的变量采用自增的方式为每个FastThreadLocal实例分配索引下标。每当创建一个新的FastThreadLocal实例时,在其构造方法中,使用AtomicInteger的getAndIncrement()方法为其分配一个索引下标。这样就解决了哈希冲突的问题。
2、内存泄漏问题。
之前的ThreadLocal在每个线程使用结束后都需要手动调用remove方法将线程本地保存的副本清除,不然只要Thread对象不被回收,ThreadLocal变量的副本就会一直保存在线程的ThreadLocalMap中。
FastThreadLocal解决此问题的方式很简单,在线程执行完任务之后,自动调用FastThreadLocal的removeAll方法将当前线程保存的FastThreadLocal型的副本全部清除。

FastThreadLocal的源码与实现原理

FastThreadLocal需要结合FastThreadLocalThread使用

上文说到,FastThreadLocal使用数组取代哈希表来保存线程本地的变量副本,JDK原生的线程类Thread自然无法修改,因此只能只能重新定义一个线程类,在里面添加一个数组类型的属性。这个线程类就是FastThreadLocalThread,里面用来存储的本地变量副本的数组是InternalThreadLocalMap类中的indexedVariables属性。

看源码,FastThreadLocalThread继承于Thread,因此是个线程类,里面含有InternalThreadLocalMap类型的属性threadLocalMap。

//FastThreadLocalThread 是Thread 的子类
public class FastThreadLocalThread extends Thread {
    //里面包含一个InternalThreadLocalMap类的属性threadLocalMap
    private InternalThreadLocalMap threadLocalMap;
    /* 无关代码不在此展示 */
}

InternalThreadLocalMap里面有一个数组indexedVariables,用来存储线程本地的变量副本。

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    //用来存储线程本地的变量副本的数组
    private Object[] indexedVariables;
    /* 无关代码不在此展示 */
}

FastThreadLocal在实例化时分配索引下标

在FastThreadLocal唯一的构造方法中,里面只做了一件事,就是为当前创建的FastThreadLocal实例分配一个索引下标。

    public FastThreadLocal() {
        //获取本FastThreadLocal对象在数组中的索引下标
        index = InternalThreadLocalMap.nextVariableIndex();
    }

下面走到InternalThreadLocalMap.nextVariableIndex()源码中:

    public static int nextVariableIndex() {
        //返回当前值并且自增
        int index = nextIndex.getAndIncrement();
        //数组下标合法性检查,不得大于最大值,也不得小于0
        if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) {
            //nextIndex的值不得超过Integer.MAX_VALUE - 8,否则有OutOfMemoryError风险
            //为什么会这样,请读者自行了解
            nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE);
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

其中的nextIndex是一个初始值为0的AtomicInteger类型的原子变量,每次调用nextVariableIndex()时都会自增一次。这里需要注意的是FastThreadLocal的索引下标是从1开始分配的,即第一个创建的FastThreadLocal实例的索引下标是1。为什么nextIndex的初始值0不用做FastThreadLocal实例的索引下标,下文会分析。

明确两个属性:threadLocalMap和threadLocals

结合上文,这两个属性需要留意,FastThreadLocal源码中经常用到:

threadLocals是Thread类中的属性,属于ThreadLocal.ThreadLocalMap类型,里面有个哈希表table用来保存ThreadLocal变量的副本。

threadLocalMap是FastThreadLocalThread中的属性,属于InternalThreadLocalMap类型,里面有个数组,用来保存FastThreadLocal变量的副本。

FastThreadLocal的set()方法

下面走进FastThreadLocal的set()方法的源码,看下FastThreadLocal是如何保存变量的。

    public final void set(V value) {
        //判断保存的值是不是UNSET这个对象,UNSET是InternalThreadLocalMap中
        //定义的一个Object对象。
        //因为InternalThreadLocalMap中用来保存变量的数组indexedVariables
        //在创建或者扩容时,会把数组上每个空位置的值都设为UNSET
        //所以这里自然不能让用户也往数组中存UNSET,不然就无法判断这个位置上的值
        //是缺省值还是用户存入的变量副本了
        if (value != InternalThreadLocalMap.UNSET) {
            //获取当前Thread的InternalThreadLocalMap属性
            InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
            //往当前Thread的InternalThreadLocalMap属性的indexedVariables数组中
            //对应的位置插入值,这个位置就是本FastThreadLocal对象的索引下标
            //并且将当前的FastThreadLocal对象保存到待清理的Set中
            setKnownNotUnset(threadLocalMap, value);
        } else {
            //如果用户set的是缺省值UNSET,使用remove()将线程保存的变量副本清除。
            //即存入UNSET,Netty就认为你需要删除当前线程的FastThreadLocal变量副本
            remove();
        }
    }

首先判断用户需要存入的是不是缺省值UNSET,如果不是的话开始往数组里插入值。
首先使用InternalThreadLocalMap.get()方法用来获取FastThreadLocalThread中的threadLocalMap属性。

    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        //如果线程是FastThreadLocalThread类型
        if (thread instanceof FastThreadLocalThread) {
            //获取其threadLocalMap属性
            return fastGet((FastThreadLocalThread) thread);
        } else {
            //否则进入slowGet()方法
            return slowGet();
        }
    }

里面做了个判断,如果线程对象是FastThreadLocalThread类型,则使用fastGet方法,获取FastThreadLocalThread中的threadLocalMap属性。

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        //获取当前线程的InternalThreadLocalMap属性
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            //如果是第一次获取,threadLocalMap为null,则创建一个
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

如果当前线程不是FastThreadLocalThread类型,Netty为了代码的统一性,则在Thread类中threadLocals(用来封装ThreadLocal使用的哈希表)中保存一个InternalThreadLocalMap对象,并将其返回。这样,后面的存取代码不用因为当前线程的对象的类型不能再做区分。

    //如果当前线程不是FastThreadLocalThread类型,
    //则在Thread类中threadLocals(ThreadLocal使用)中
    //保存一个InternalThreadLocalMap对象,并将其返回
    private static InternalThreadLocalMap slowGet() {
        //slowThreadLocalMap是一个ThreadLocal<InternalThreadLocalMap>类型的对象
        //调用其get方法时,直接从Thread类中threadLocals(ThreadLocal使用)中获取
        //线程本地保存的InternalThreadLocalMap实例
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        //如果线程的threadLocals中没有保存InternalThreadLocalMap实例
        if (ret == null) {
            //创建一个存入
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }

这里我们能发现一个问题,FastThreadLocal一定要结合FastThreadLocalThread使用,否则仍然会使用JDK原生的ThreadLocal来进行变量副本在线程本地的保存和读取。并且在线程本地的哈希表中(Thread类中的threadLocals属性中封装的table)还不是变量的值,而是使用InternalThreadLocalMap又将变量包裹了一层因此,如果FastThreadLocal不结合FastThreadLocalThread使用的话,速度会变得比ThreadLocal更慢。

数据结构如下,网上盗的图,感觉画的不清楚,有时间笔者自己画一个。
在这里插入图片描述
再获取到FastThreadLocalThread中的threadLocalMap属性后,执行setKnownNotUnset(threadLocalMap, value)方法,将变量value存入threadLocalMap之中。

    /*
    * threadLocalMap:当前FastThreadLocalThread的InternalThreadLocalMap属性
    * */
    private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
        /*
        * 往当前Thread的InternalThreadLocalMap属性的indexedVariables数组中,
        * 在index位置上插入value值
        * */
        if (threadLocalMap.setIndexedVariable(index, value)) {
            // 并将当前的FastThreadLocal对象保存到待清理的Set中
            addToVariablesToRemove(threadLocalMap, this);
        }
    }

分为两步:
第一步:使用setIndexedVariable方法将变量存入threadLocalMap之中。
下面进入setIndexedVariable方法的源码。

public boolean setIndexedVariable(int index, Object value) {
        //获取数组
        Object[] lookup = indexedVariables;
        //如果下标在数组长度范围之内
        if (index < lookup.length) {
            Object oldValue = lookup[index];
            //值存入数组中
            lookup[index] = value;
            //返回数组中对应位置以前是否存过变量
            return oldValue == UNSET;
        } else {
            //否则数组扩容
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }

第二步:使用addToVariablesToRemove方法并将当前的FastThreadLocal对象保存到待清理的Set中。
为了方便清理线程中存储的的FastThreadLocal变量副本,threadLocalMap中的数组indexedVariables的第1个位置(下标为0)不用了存储变量副本,而是存了一个Set集合。这个Set集合里面保存了全部的FastThreadLocal实例。等线程需要释放所有FastThreadLocal变量副本时,直接遍历这个Set集合再做处理即可。

    /*
    *
    * 1)取下标index为0的数据(用于存储待清理的FastThreadLocal对象Set集合中),
    * 如果该数据是缺省值UNSET或null,则会创建FastThreadLocal对象Set集合,并将
    * 该Set集合填充到下标index为0的数组位置。
    * 2)如果该数据不是缺省值UNSET,说明Set集合已金被填充,直接强转获取该Set集合。
    * 3)最后将FastThreadLocal对象保存到待清理的Set集合中。
    *
    * threadLocalMap:当前线程的InternalThreadLocalMap属性
    * variable:当前FastThreadLocal对象
    * */
    @SuppressWarnings("unchecked")
    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        // 取下标index为0的数据,用于存储待清理的FastThreadLocal对象Set集合中
        // 用于存储待清理的FastThreadLocal对象Set集合
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        Set<FastThreadLocal<?>> variablesToRemove;
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            //下标index为0的数据为空,则创建存储FastThreadLocal对象Set集合
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            //将InternalThreadLocalMap中下标为0的数据,设置成FastThreadLocal对象Set集合
            threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
        } else {
            //转为Set类型
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }

        //将FastThreadLocal对象保存到待清理的Set中
        variablesToRemove.add(variable);
    }

FastThreadLocal的get()方法

get方法相对简单多,只需要从threadLocalMap中的数组里面取值即可。

    public final V get() {
        //获取当前线程的InternalThreadLocalMap属性
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        //获取当前线程的InternalThreadLocalMap属性中indexedVariables数组中,
        //对应索引位置的值
        Object v = threadLocalMap.indexedVariable(index);
        //如果索引位置上不是缺省值
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }
        //如果线程中没有存储当前FastThreadLocal的属性值,
        //调用初始化方法
        return initialize(threadLocalMap);
    }

FastThreadLocal的清理

FastThreadLocal提供了一个可以用来清理当前线程存储的所有FastThreadLocal变量的方法,可以在线程执行任务结束时使用。防止线程对象没有垃圾回收,导致线程存储的所有FastThreadLocal变量都不能被垃圾回收。

    /*
    *  清理当前线程存储的所有FastThreadLocal变量
    * */
    public static void removeAll() {
        //获取当前线程的InternalThreadLocalMap属性
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
        if (threadLocalMap == null) {
            return;
        }

        try {
            //线程的InternalThreadLocalMap属性中的indexedVariables数组
            //的第一个元素是用来存储已有FastThreadLocal变量的Set集合
            Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
            //如果这个集合不是null
            if (v != null && v != InternalThreadLocalMap.UNSET) {
                @SuppressWarnings("unchecked")
                Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
                //将set集合转为数组
                FastThreadLocal<?>[] variablesToRemoveArray =
                        variablesToRemove.toArray(new FastThreadLocal[0]);
                //移除所有已存储的值
                for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                    tlv.remove(threadLocalMap);
                }
            }
        } finally {
            //清除线程的threadLocalMap属性
            InternalThreadLocalMap.remove();
        }
    }

里面做了两件事情,一是遍历数组的0位置上存储的Set集合,取出所有FastThreadLocal实例,然后依次调用其remove方法。

    public final void remove(InternalThreadLocalMap threadLocalMap) {
        if (threadLocalMap == null) {
            return;
        }
        //数组的index位置重置为UNSET
        Object v = threadLocalMap.removeIndexedVariable(index);
        //Set集合中删除当前FastThreadLocal对象
        removeFromVariablesToRemove(threadLocalMap, this);
        //如果数组的index位置上原本有值
        if (v != InternalThreadLocalMap.UNSET) {
            try {
                //onRemoval留给子类实现
                onRemoval((V) v);
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
        }
    }

二是清除线程的threadLocalMap属性,InternalThreadLocalMap.remove()需要做的事情。

    public static void remove() {
        Thread thread = Thread.currentThread();
        //如果线程是FastThreadLocalThread对象
        if (thread instanceof FastThreadLocalThread) {
            //将FastThreadLocalThread对象的threadLocalMap属性设为null,方便垃圾回收
            ((FastThreadLocalThread) thread).setThreadLocalMap(null);
        } else {
            //清理Thread类中的threadLocals中的哈希表
            //调用的是ThreadLocal的remove()方法
            slowThreadLocalMap.remove();
        }
    }

FastThreadLocalRunnable的作用

FastThreadLocalRunnable是一个实现Runnable接口的类,因此也是一个可被线程执行的任务。但是在其run()方法却执行了另一个Runnable对象的run()方法。表面上看上去怪怪的,其实这是对原Runnable对象的run()方法进行增强,让Runnable对象的run()方法执行完后,再增加执行一个FastThreadLocal.removeAll(),来清理线程存储的所有FastThreadLocal变量。
其源码比较简单:

final class FastThreadLocalRunnable implements Runnable {
    //原Runnable对象
    private final Runnable runnable;

    private FastThreadLocalRunnable(Runnable runnable) {
        this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
    }

    //run方法,对原Runnable对象的run方法进行增强
    @Override
    public void run() {
        try {
            //执行原Runnable对象的run方法
            runnable.run();
        } finally {
            //增强内容:清除当前线程存储的所有FastThreadLocal变量
            FastThreadLocal.removeAll();
        }
    }

    //将Runnable对象封装成FastThreadLocalRunnable对象
    static Runnable wrap(Runnable runnable) {
        return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
    }
}

一般在需要使用FastThreadLocal时,不仅需要使用创建FastThreadLocalThread对象的方式来创建线程,也需要使用FastThreadLocalRunnable的wrap(Runnable runnable)方法将原本线程需要执行的Runnable对象封装成FastThreadLocalRunnable对象,对原Runnable对象的run方法进行增强。让线程执行结束后,自动调用FastThreadLocal.removeAll()清理FastThreadLocal变量。

这里可以回顾一下我在《Netty源码解析之线程池的实现(二):创建线程与执行任务》文章写到的线程工厂DefaultThreadFactory里面创建线程的方法。

    @Override
    public Thread newThread(Runnable r) {
        //1、将Runnable任务封装成FastThreadLocalRunnable
        //2、拼接线程名称
        //3、调用同名的newThread方法
        Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
        /*省略无关代码*/
        return t;
    }

    //创建一个线程实例,FastThreadLocalThread
    protected Thread newThread(Runnable r, String name) {
        //创建线程,传入增强后的Runnable对象
        return new FastThreadLocalThread(threadGroup, r, name);
    }

可以看出,线程工厂DefaultThreadFactory里面创建线程时,就是将原Runnable对象封装成FastThreadLocalRunnable,并结合FastThreadLocalThread来使用FastThreadLocal!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值