【Netty】FastThreadLocal比ThreadLocal快之源码解析

news2024/11/13 14:55:39

ThreadLocal

【并发设计模式】聊聊线程本地存储模式如何实现的线程安全
【Java并发】从simpleDateFormart聊聊threadlocal原理机制

前两篇中已经从源码角度进行剖析,本篇主要从设计角度剖析。

在并发中为了保证数据安全,可以采用数据隔离的方式 也就是大家都自己内部维护一份数据,在实际落地的时候,其实就是针对每个线程内部持有自己特殊的数据。ThreadLocal就是一个案例。

那么我们思考下,如果你是JDK的设计者你会如何设计,一般就是使用一个全局Map<Thread,具体数据>,显然是用这种方式,在写操作的时候,需要进行加锁,无论是lock 还是CAS的方式,都存在一定的性能损耗。

那么ThreadLocal是如何做的。因为数据是对于线程级别的,所以对于Thread来说,内部持有一个ThreadLocalMap,而这个就是一个Entry数组,Key是ThreadLocal,Value是具体数据。
在这里插入图片描述
在这里插入图片描述

优秀设计

线程内数据隔离,避免锁竞争

ThreadLocal巧妙使用内部持有一个私有的ThreadLocalMap来存储数据。可以避免锁竞争。

易用性,封装复杂性

在使用的时候,其实对于程序员来说,不需要了解具体的内部细节,直接操作ThreadLocal的set get就可以获取数据,使用起来比较好用,封装内部复杂性。

弱引用避免内存泄漏

其实主要就是两点 一个依赖于Key是软引用,当ThreadLocal外部不在使用的时候,就会进行下次GC的时候,回收,但是对应的value还存在,如何避免,那就是使用remove() , 当前 本身 get \ set 也会清除key=null 的数据。

使用线性探测法,而不是拉链法

	int i = key.threadLocalHashCode & (len-1);

在进行数据set的时候,使用的hash,那么有hash一定会存在hash冲突,ThreadLocalMap是如何解决的,答案是下探法。
在这里插入图片描述
一句话就是,如果当前位置有数据,那么就延续后一个位置,如果都满了,在进行扩容操作。

那么为什么ThreadLocalMap要这么设计,其实主要就是两点,第一个本身 时间效率存储的数据不多,并且ThreadLocalMap是一个数组,可以有效的利用内存数据的连续性 内存的预读性, 高效的查找和存储数据,第二点,空间效率,如果采用拉链法,那么就需要多存储一个next指针,占用一定的空间。

既然ThreadLocal已经这么优秀了,那么为什么还有Netty实现一个呢,答案其实就只要应对的场景不同,ThreadLocal本身只是提供一些少量数据的存储,对于支持高并发来说,极端情况下,线性探测法时间复杂度在O(N),以及在数据满之后,要进行的Rehash操作。当如还有内存泄漏的问题。

FastThreadLocal

最佳实践

具体使用其实就是通过创建FastThreadLocalThread 然后通过 FastThreadLocal 设置值。

        final FastThreadLocal fastThreadLocal = new FastThreadLocal();
        final FastThreadLocal fastThreadLocal2 = new FastThreadLocal();
        final FastThreadLocal fastThreadLocal3 = new FastThreadLocal();
        FastThreadLocalThread fastThreadLocalThread = new FastThreadLocalThread(()-> {
            fastThreadLocal.set("1");
            fastThreadLocal.get();

            fastThreadLocal2.set("2");
            fastThreadLocal2.get();

            fastThreadLocal3.set("3");
            fastThreadLocal3.get();

            Thread thread = Thread.currentThread();
            System.out.println(thread);
        },"T1");

        fastThreadLocalThread.start();

在这里插入图片描述
在这里插入图片描述

源码解析

初始化

FastThreadLocalThread的初始化,因为内部持有一个threadLocalMap引用。

public class FastThreadLocalThread extends Thread {
    private InternalThreadLocalMap threadLocalMap;
}

当我们创建一个FastThreadLocal对象的时候,会通过全局的InternalThreadLocalMap#nextVariableIndex 方法创建一个全局唯一自增ID,CAS的方式进行获取。比如当前获取的index=1

    public FastThreadLocal() {
        // 创建一个全局的唯一下标记
        index = InternalThreadLocalMap.nextVariableIndex();
    }
    
    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        return index;
    }

Set

当设置一个值的时候,先判断当前值是否是默认值,如果不是那么进行设置,如果是的话进行执行remove()的逻辑。

1.先获取InternalThreadLocalMap
2.设置对应的值

    public final void set(V value) {
        // value = unset 进行remove
        if (value != InternalThreadLocalMap.UNSET) {
            // 获取当前线程的InternalThreadLocalMap
            InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
            setKnownNotUnset(threadLocalMap, value);
        } else {
            // value是Object 初始化
            // 把当前FastThreadLocal的操作 重置
            // 1.对应的位置的值 设置成UNSET
            // 2.set集合中里面的FastThreadLocal 删除
            remove();
        }
    }

1.先获取当前线程
2.判断当前是否是FastThreadLocalThread 或者普通Thread,进行不同的处理。

    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        // 当前是线程类型是FastThreadLocalThread
        if (thread instanceof FastThreadLocalThread) {
            // 直接从FastThreadLocalThread 获取internalThreadLocalMap
            return fastGet((FastThreadLocalThread) thread);
        } else {
            // 普通的threadLocal 时机上很对于普通的thread 也可以使用internalThreadLocalMap
            return slowGet();
        }
    }

1.从当前线程获取,如果为空,直接new一个 并进行赋值操作。

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

设置值

    /**
     * 存储数据
     * 按照index 对应数组的下表存储数据
     * @see InternalThreadLocalMap#setIndexedVariable(int, Object).
     */
    private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
        // 按照index 存储进去
        if (threadLocalMap.setIndexedVariable(index, value)) {
            // 把fastThreadLocal对象 存储到set object[0]
            addToVariablesToRemove(threadLocalMap, this);
        }
    }

这里其实就是先判断index是否超过数组范围,如果没有超过范围,那么就执行赋值,否则就需要扩容在赋值操作。

    public boolean setIndexedVariable(int index, Object value) {
        Object[] lookup = indexedVariables;
        // 在范围内的
        if (index < lookup.length) {
            // 获取可能存在的老值
            Object oldValue = lookup[index];
            // 赋值操作
            lookup[index] = value;
            // 等于object
            // true : 说明在这个位置 没有存过东西
            // false : 说明这个位置 已经存在过别的数据 等于覆盖
            return oldValue == UNSET;
        } else {
            // 扩容处理
            // 扩容的标准 使用超过多少 -- index扩容
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }

这里是核心,需要维护添加新的数据,存储在0号位置。大概就是看0号位置有没有数据,没有创建一个Set 对象

    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        Object v = threadLocalMap.indexedVariable(VARIABLES_TO_REMOVE_INDEX);
        Set<FastThreadLocal<?>> variablesToRemove;
        // 第一次进来 创建一个SET集合 Object数组的0号位置
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            // set 存储到0号位置
            threadLocalMap.setIndexedVariable(VARIABLES_TO_REMOVE_INDEX, variablesToRemove);
        } else {
            // 拿到set集合
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }
        // 将fastThreadLocal 添加到set中
        variablesToRemove.add(variable);
    }

在这里插入图片描述

Get

获取数据比较简单, 其实直接通过index获取就可以。当然考虑到可能没有数据,initialize就是一个拓展兼容处理。

    public final V get() {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        Object v = threadLocalMap.indexedVariable(index);
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }
        return initialize(threadLocalMap);
    }

在这里插入图片描述

优秀设计

快在哪里

空间换时间,ThreadLocal慢在线性探测,那么直接通过更大数组空间的开辟,避免线性探测,这是一种空间换时间的思想,而FastThreadLocal就是这么做的。通过数组的方式进行获取查找数据。O(1)的时间复杂度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2094735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信视频号 点赞+关注+喜欢+发布评论

微信视频号 点赞关注喜欢发布评论 微信视频号 点赞关注喜欢发布评论

3127. 构造相同颜色的正方形(24.8.31)

题目 给你一个二维 3x3 的矩阵 grid&#xff0c;每个格子都是一个字符&#xff0c;要么是 B &#xff0c;要么是 W。字符 W 表示白色&#xff0c;字符 B 表示黑色。 你的任务是改变至多一个格子的颜色&#xff0c;使得矩阵中存在一个 2x2 颜色完全相同的正方形。 如果可以得到…

这位沉默寡言忠厚老实的商人遇到了哪位神奇女子搭救?

这位沉默寡言忠厚老实的商人遇到了哪位神奇女子搭救&#xff1f; 接下来&#xff0c;我将为你讲述一个关于侠女的故事&#xff0c;她救助了一个落难之人&#xff0c;并发表了许多关于剑侠的独特见解&#xff0c;这些都是前所未有的&#xff0c;简直精彩绝伦。有诗为证&#xff…

APP、3D动画效果统计图、WEB页面自定义、电话短信通知、告警等级自动升级、告警短视频、人工巡查等功能介绍的智慧矿山多模态智能分析预警平台

智慧矿山多模态预警平台是一个集成了多种实用功能和先进技术的综合管理系统&#xff0c;目标是提高矿山的安全性和管理效率。下面介绍多模态智能分析预警平台的特色功能&#xff1a; 智慧矿山多模态首页 首先看看智慧矿山多模态首页 智慧矿山多模态首页包含了很多内容&#x…

MySQL查询全解

接下来我们就要进入重中之重-----MySQL查询的学习中了.激动激动&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 1 聚合查询 常见的统计总数、计算平局值等操作&#xff0c;可以使用聚合函数来实现&#xff0c;常见的聚合函数有&#xff1a; COUN…

打字侠又发布更新啦,新增小学生课文打字

历经一个月的时间&#xff0c;打字侠又发布了新的功能需求啦&#x1f389;&#x1f389;&#x1f389; 新功能1&#xff1a;提供面向小学生的课文打字练习 具体包括&#xff1a;从小学二年级到小学六年级的中文和英文词汇&#xff0c;总共约2000个中文词组和1600个英文单词。这…

不容错过!最简单的Cursor无限注册攻略!

文章目录 前言具体用法下载程序登陆授权查看支持的模型 如何使用引用 前言 Cursor 是一个编程软件,就像Vscode一样,可以让你轻松地编写、调试和运行代码。不一样的是它内置了8个热门AI模型,什么GPT4、Claude3最新模型,都能免费试用14天,我们只需要任意邮箱注册账号,然后下载程序…

多目标应用:基于多目标雾凇算法(MORIME)的移动机器人路径规划研究(提供MATLAB代码)

一、机器人路径规划介绍 移动机器人&#xff08;Mobile robot&#xff0c;MR&#xff09;的路径规划是 移动机器人研究的重要分支之&#xff0c;是对其进行控制的基础。根据环境信息的已知程度不同&#xff0c;路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或…

Leetcode3243. 新增道路查询后的最短距离 I

Every day a Leetcode 题目来源&#xff1a;3243. 新增道路查询后的最短距离 I 解法1&#xff1a;广度优先搜索 暴力。 每次加边后重新跑一遍 BFS&#xff0c;求出从 0 到 n−1 的最短路。 代码&#xff1a; /** lc appleetcode.cn id3243 langcpp** [3243] 新增道路查询…

一款免费开源功能强大的截图工具

Ksnip是一款功能强大的跨平台开源截图工具&#xff0c;基于Qt开发&#xff0c;支持Linux、Windows和macOS操作系统。Ksnip的主要特点包括多种截图模式和丰富的标注功能。 核心功能 截图模式&#xff1a;自定义矩形区域截图全屏截图当前显示器截图活动窗口截图鼠标下的窗口截图…

Eureka原理大起底:从菜鸟到高手,轻松玩转服务注册与发现的艺术!Eureka不只是个名字,它是微服务世界的‘万能钥匙’,解锁无限可能!

第一章 引言 Eureka原理&#xff0c;作为服务发现领域的一个重要理论&#xff0c;对于构建高可用的分布式系统具有指导意义。随着微服务架构的兴起&#xff0c;服务之间的发现和通信变得尤为关键&#xff0c;Eureka原理为解决这一问题提供了有效的方案。本文旨在深入探讨Eurek…

【精选】基于Hadoop的用户网站浏览分析的设计与实现(全网最新定制,独一无二)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

联盟 | ITNIO TECH颂量 X PartnerShare,助力跨境企业高效出海

*本文章转载自ITNIO TECH颂量 在全球化浪潮中&#xff0c;高效是出海企业在全球市场竞争中获胜的重要秘诀之一。因此&#xff0c;跨境企业除了需要单一的技术或服务&#xff0c;还需要能够整合多种资源、提供一站式解决方案的合作伙伴。ITNIO TECH颂量宣布与SaaS推广分销联盟系…

前端面试题-手撕代码题

1. 实现 Promise.all&#xff0c;或者 实现 Promise.allSettled &#xff08;1&#xff09;promise.all: 传入一个promise数组&#xff0c;其中所有promise都变为成功状态&#xff0c;返回一个数组&#xff0c;数组内是各个promise的返回&#xff1b;若任意传入的promise变为…

16:螺丝孔和MARK点布局

正版mark布局 ①正面3个。背面3个 ②离板边5mm 螺丝孔 板边

Mako 试玩|编译速度6到飞起!

嗨&#xff01;我是小谷&#xff0c;大家好久不见&#xff5e; 今天想和大家分享的技术是 Mako , 一款编译构建速度比 webpack 快 10 倍&#xff5e;100 倍的前端构建工具。 网上有传言将 Mako 比作前端脚手架里的 鲨鱼心脏 &#xff0c;有了它&#xff0c;前端工程师工作的幸…

重塑未来:碳捕集与存储(CCS)的革命性突破与可持续发展路径

随着全球气候变化的加剧&#xff0c;减少二氧化碳&#xff08;CO₂&#xff09;排放已成为应对气候变化的关键任务之一。碳捕集与存储&#xff08;CCS&#xff09;技术因其能够直接从源头捕捉CO₂并将其安全存储&#xff0c;避免其进入大气层&#xff0c;而受到广泛关注。CCS被…

QT做一个USB HID设备识别软件

1.下载 HidApi库&#xff1a;GitHub - yigityuce/HidApi: Human Interface Device Api (HidApi) with C 2.pro文件添加 DEFINES - UNICODE LIBS -lsetupapi 3.建立三个对象 HidApi hidApi;HidDevice hidDev;//HID设备HidDeviceList devList;//HID设备列表 4.对 HID 设备进…

大连网站建设手机网页页面设计

在现代社会&#xff0c;随着智能手机的普及&#xff0c;越来越多的用户选择通过手机访问网站&#xff0c;这使得移动端网页设计的重要性日益凸显。大连作为一个经济和文化中心&#xff0c;网站建设行业也在不断发展。针对大连的网站建设&#xff0c;手机网页页面设计需要特别注…

在街子古镇游的台湾自媒体人

刚刚看到《网易首页 > 网易号》于昨&#xff08;8月31日逾23时&#xff09;天深夜发布《崇州看点》的新闻报道《台湾自媒体人天府游记&#xff0c;被崇州的这个地方深深吸引……》&#xff0c;虽觉得新鲜但并不感到惊奇。因为在去年12月5日&#xff0c;《人民日报海外版》和…