Netty源码剖析之FastThreadLocal机制

这篇具有很好参考价值的文章主要介绍了Netty源码剖析之FastThreadLocal机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

版本信息:

JDK1.8

Netty-all:4.1.38.Final

传统的ThreadLocal机制

讲netty的FastThreadLocal机制,就不得不提及到JDK自带的ThreadLocal机制,所以下面会用一小段篇幅介绍一下ThreadLocal机制~

ThreadLocal的机制,大致的解释为线程本地变量,独属于线程,所以每个线程有一份,所以互相独立、隔离、线程安全、线程中任意地方可存可取。在Java中Thread类中存在一个集合用于实现此功能。

public class Thread implements Runnable {
    …………

    ThreadLocal.ThreadLocalMap threadLocals = null;

    …………
}

Netty源码剖析之FastThreadLocal机制,源码解读,Netty源码,java,netty,多线程

所以我们需要大致看一下如何使用,获取元素的源码如下: 

public T get() {
    // 获取到线程本地集合。
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);

    if (map != null) {
        // 根据ThreadLocal的Hash值得到集合的元素
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果没有线程本地集合,那就创建一个。
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
    // 根据Hash值来定位索引
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        // 直接命中,就返回
        return e;
    else
        // 如果冲突,往后面找
        return getEntryAfterMiss(key, i, e);
}

我们清楚,Hash运算虽然是O1时间复杂度,但是这只是最理想状态,因为无法避免Hash碰撞,当元素多起来,甚至能达到On的时间复杂度。而JDK原生的ThreadLocal就是通过Hash来计算索引下标,当发生Hash碰撞往后找临近节点插入,并不能达到理想的O1时间复杂度。

Netty中FastThreadLocal机制

上文介绍了JDK自带的ThreadLocal机制,他是根据Hash算法来存放节点,因为Hash碰撞的原因所以达不到O1的时间复杂度,而网传 " Netty的FastThreadLocal机制比它快3倍 " 所以下文介绍FastThreadLocal到底快在那里,如何实现的。

在这之前还需要介绍一个类 FastThreadLocalThread ,从上文我们知道JDK自带的ThreadLocal机制是通过Thread对象中ThreadLocal.ThreadLocalMap 集合存放数据,而 Netty的FastThreadLocal肯定也是需要有一个容器来存放数据,所以Netty设计了FastThreadLocalThread类,它实现与原生的Thread类,在其中定义了InternalThreadLocalMap集合。

public class FastThreadLocalThread extends Thread {

    …………

    private InternalThreadLocalMap threadLocalMap;

    …………
}

不妨看一下 InternalThreadLocalMap 类的定义,可以看到内部比较简单,实现UnpaddedInternalThreadLocalMap类,在UnpaddedInternalThreadLocalMap类中定义了一个数组用于存放FastThreadLocal的对象,还有兼容原生线程的ThreadLocal,以及一个原子类用于产出索引值。

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
}

class UnpaddedInternalThreadLocalMap {

    // 用于兼容,当线程非FastThreadLocalThread的情况下使用。
    static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
    
    // 用于原子性的产出索引值。
    // 注意这个变量是被static final修饰的
    static final AtomicInteger nextIndex = new AtomicInteger();

    // 存放FastThreadLocal管理的对象。
    Object[] indexedVariables;
}

铺垫做好了,接下来就看到FastThreadLocal类实现。

public class FastThreadLocal<V> {

    // 注意这里是static final修饰的,所以在clinit的时候初始化
    // InternalThreadLocalMap.nextVariableIndex() 这个方法是得到索引值
    // 所以这个值恒定为0,用作与特殊用途
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

    // 索引值
    private final int index;

    public FastThreadLocal() {
        // 在构造方法中算出当前FastThreadLocal所属的索引值,而这个索引值用于InternalThreadLocalMap中。
        index = InternalThreadLocalMap.nextVariableIndex();
    }
}

public static int nextVariableIndex() {
    // 因为nextIndex是static修饰的,所以这里全局唯一。
    // 原子性自增,得到索引值。
    int index = nextIndex.getAndIncrement();
    if (index < 0) {
        nextIndex.decrementAndGet();
        throw new IllegalStateException("too many thread-local indexed variables");
    }
    return index;
}

在FastThreadLocal类中有一个索引值,此索引值在构造方法中初始化,目的是原子性的获取到当前FastThreadLocal的索引,而这个索引用途在于InternalThreadLocalMap 直接O1的时间复杂度找到元素。而因为每创建一个FastThreadLocal,UnpaddedInternalThreadLocalMap类中被static修饰的全局原子类索引值就会+1,所以就避免了重复的可能性。

Netty源码剖析之FastThreadLocal机制,源码解读,Netty源码,java,netty,多线程

 接下来,就看到如何使用FastThreadLocal,当然是通过get方法获取,获取不到就创建,然后放入集合中,下次就能够命中。这个跟ThreadLocal一模一样的思想。

public class FastThreadLocal<V> {

    public final V get() {
        // 取出线程中InternalThreadLocalMap集合
        // 当然必须是FastThreadLocalThread线程,如果是原生Thread对象的话,会使用原生ThreadLocal对象来兼容
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();

        Object v = threadLocalMap.indexedVariable(index);
        // 命中就直接返回
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }

        // 没命中就创建
        return initialize(threadLocalMap);
    }

    private V initialize(InternalThreadLocalMap threadLocalMap) {
        V v = null;
        try {
            // 模板方法,给子类实现。
            v = initialValue();
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }

        // 添加到线程对象的InternalThreadLocalMap集合中,下次就能够直接命中了。
        threadLocalMap.setIndexedVariable(index, v);

        // 把当前FastThreadLocal添加到移除队列中,当执行完毕后,Netty自动帮你回收空间。
        addToVariablesToRemove(threadLocalMap, this);
        return v;
    }

    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        
        // 拿到variablesToRemoveIndex索引的值,而variablesToRemoveIndex恒定为0。
        // 也即0号特殊用途。
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);

        Set<FastThreadLocal<?>> variablesToRemove;

        // 第一次是UNSET,所以去初始化
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            // 创建一个集合,用于存放已经使用的FastThreadLocal,后续用于自动资源回收
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
        } else {
            // 后续只需要取出来即可。
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }

        // 把当前FastThreadLocal添加到集合中。
        variablesToRemove.add(variable);
    }
}

对以上FastThreadLocal的get方法做一个总结:

  1. 获取到FastThreadLocalThread线程的InternalThreadLocalMap集合,如果是原生的Thread线程就会使用ThreadLocal兼容InternalThreadLocalMap集合(所以,如果使用FastThreadLocal,不使用FastThreadLocalThread线程,那么效率会比原生的还低)
  2. 通过FastThreadLocal的索引从InternalThreadLocalMap集合中获取到元素
  3. 如果命中就直接返回
  4. 如果不命中就调用initialize做创建和初始化工作
  5. 创建工作调用initialValue方法,此方法是一个模板方法,交给子类使用,所以具体的创建交给子类
  6. 创建完毕后,添加到InternalThreadLocalMap集合中,下次就可以直接命中
  7. 最后会调用addToVariablesToRemove方法,此方法会把InternalThreadLocalMap的0号索引放入一个Set<FastThreadLocal<?>> 集合,用于后续的自动回收资源。

所以上述的图需要做出修正,因为InternalThreadLocalMap的0号索引有特殊用途。用于自动回收资源,后续马上介绍。

Netty源码剖析之FastThreadLocal机制,源码解读,Netty源码,java,netty,多线程

对于自动回收资源,其实实现的原理非常简单,我们看到FastThreadLocalThread的构造方法。这里使用了最经典的装饰者模式,把原有的Runnable给做了增强,当线程执行完run方法,也即执行完业务逻辑后,在finally代码块中做资源回收,所以也称之为自动回收资源。

public class FastThreadLocalThread extends Thread {
    public FastThreadLocalThread(Runnable target) {
        super(FastThreadLocalRunnable.wrap(target));
        cleanupFastThreadLocals = true;
    }
}

final class FastThreadLocalRunnable implements Runnable {

    private final Runnable runnable;

    @Override
    public void run() {
        try {
            runnable.run();
        } finally {
            // 资源回收
            FastThreadLocal.removeAll();
        }
    }

    static Runnable wrap(Runnable runnable) {
        return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
    }
}

最后再看一下,资源回收的代码逻辑。

public static void removeAll() {
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
    try {
        // 获取到0号索引的set集合
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        if (v != null && v != InternalThreadLocalMap.UNSET) {
            @SuppressWarnings("unchecked")
            Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
            FastThreadLocal<?>[] variablesToRemoveArray =
            variablesToRemove.toArray(new FastThreadLocal[0]);
            // 遍历set集合中存放的所有的FastThraedLocal对象。
            for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                // 调用FastThraedLocal的remove方法。
                tlv.remove(threadLocalMap);
            }
        }
    } finally {
        InternalThreadLocalMap.remove();
    }
}

public final void remove(InternalThreadLocalMap threadLocalMap) {
    // 从集合中删除当前节点
    Object v = threadLocalMap.removeIndexedVariable(index);
    removeFromVariablesToRemove(threadLocalMap, this);

    if (v != InternalThreadLocalMap.UNSET) {
        try {
            // 模板方法,具体的回收资源细节,交给子类实现。
            onRemoval((V) v);
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }
    }
}

这里也非常的简单,就是拿到InternalThreadLocalMap 中0号索引对应的set集合,此集合中存放着所有的FastThreadLocal,然后一一调用FastThreadLocal的remove方法,而remove方法中调用了onRemoval方法,此方法是个标准的模板方法,具体的回收细节交给子类实现,父类只帮你开启自动回收机制,回收细节还是暴露给开发者~文章来源地址https://www.toymoban.com/news/detail-685443.html

总结:

  1. FastThreadLocal完全是O1时间复杂度,不过是空间换时间罢了,存在一定的内存浪费(尤其是项目中大量使用FastThreadLocal,每个线程的InternalThreadLocalMap会越来越大,浪费会越来越多,不过,如今内存不值钱,用很小很小一部分内存换取执行效率是值得的~!)
  2. FastThreadLocal一定要配合FastThreadLocalThread线程使用,不然快速特性完全使用不到,并且会降至使用原生ThreadLocal机制,最终导致效率比原生ThreadLocal机制还慢(因为存在一定的包装)
  3. FastThreadLocal虽然有自动回收资源机制,但是的子类还是需要实现onRemoval方法,回收的细节交给开发者,比如一些堆外内存的释放等等~

到了这里,关于Netty源码剖析之FastThreadLocal机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Netty】Netty中的超时处理与心跳机制(十九)

    回顾Netty系列文章: Netty 概述(一) Netty 架构设计(二) Netty Channel 概述(三) Netty ChannelHandler(四) ChannelPipeline源码分析(五) 字节缓冲区 ByteBuf (六)(上) 字节缓冲区 ByteBuf(七)(下) Netty 如何实现零拷贝(八) Netty 程序引导类(九) Reactor 模型(十) 工作原理

    2024年02月06日
    浏览(38)
  • Netty-Netty源码分析流程图

     补充

    2024年01月23日
    浏览(53)
  • 13.Netty源码之Netty中的类与API

    Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,Netty 中ServerBootstrap 是服务端启动引导类。 java //泛型 AbstractBootstrapB extends AbstractBootstrapB, C, C extends Channel ​ ServerBootstrap extends AbstractBootstrapServerBootstrap, ServerChannel ​

    2024年02月15日
    浏览(53)
  • 34.Netty源码之Netty如何处理网络请求

    通过前面两节源码课程的学习,我们知道 Netty 在服务端启动时会为创建 NioServerSocketChannel,当客户端新连接接入时又会创建 NioSocketChannel,不管是服务端还是客户端 Channel,在创建时都会初始化自己的 ChannelPipeline。 如果把 Netty 比作成一个生产车间,那么 Reactor 线程无疑是车间

    2024年02月11日
    浏览(36)
  • Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制

    Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制。该系列教程从Linux内核的各个模块入手,逐一分析其源码实现,并结合实际应用场景进行讲解。通过学习本系列,读者可以深入了解Linux操作系统的底层机制,掌握

    2024年01月21日
    浏览(47)
  • [Netty源码] Netty轻量级对象池实现分析 (十三)

    1.对象池技术介绍 对象池其实就是缓存一些对象从而避免大量创建同一个类型的对象, 类似线程池。对象池缓存了一些已经创建好的对象, 避免需要的时候创建。同时限制了实例的个数。 池化技术最终要的就是重复的使用池内已经创建的对象。 创建对象的开销大 会创建大量的

    2023年04月18日
    浏览(44)
  • 《Netty》从零开始学netty源码(四十九)之PoolArena

    Netty中分配内存是委托给PoolArena来管理的,它主要有两个实现类: 默认情况下使用的DirectArena,它的数据结构如下: 从属性中我们看到PoolArena主要分配三种类型的内存,小于32KB的分配small类型的PoolSubpage,存储在smallSubpagePools,32KB~4MB的分配normal类型的PoolChunk,根据其利用率的

    2024年02月02日
    浏览(34)
  • 《Netty》从零开始学netty源码(五十四)之PoolThreadLocalCache

    前面讲到 PoolThreadCache ,它为线程提供内存缓存,当线程需要分配内存时可快速从其中获取,在Netty中用 PoolThreadLocalCache 来管理 PoolThreadCache ,它的数据结构如下: PoolThreadLocalCache 相当于java的 ThreadLocal ,我们知道 ThreadLocal 中维护的是 ThreadLocalMap ,使用hashcode来做下标,而N

    2024年02月03日
    浏览(33)
  • 《Netty》从零开始学netty源码(五十九)之ServerBootstrapAcceptor

    前面初始化channel的过程中向pipeline中添加了一个channelHandler,即 ServerBootstrapAcceptor ,它的作用主要是将worker组的channel进行注册,它的数据结构如下: 它的属性主要是通过 ServerBootstrap 启动类设置的,它的方法主要是 channelRead() 方法,其过程如下: 在第五十八篇中,当EventLo

    2024年02月05日
    浏览(58)
  • 《Netty》从零开始学netty源码(四十六)之PooledByteBuf

    Netty中一大块内存块 PoolChunk 默认大小为4MB,为了尽可能充分利用内存会将它切成很多块 PooledByteBuf , PooledByteBuf 的类关系图如下: PooledUnsafeDirectByteBuf 与 PooledUnsafeHeapByteBuf 直接暴露对象的底层地址。 PooledByteBuf 的创建过程开销很大,高并发情况下进行网络I/O时会创建大量的

    2024年02月01日
    浏览(86)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包