FastThreadLocal 快在哪里 ?

FastThreadLocal 快在哪里 ?

  • 引言
  • FastThreadLocal
    • set
      • 如何获取当前线程私有的InternalThreadLocalMap ?
      • 如何知道当前线程使用到了哪些FastThreadLocal实例 ?
    • get
    • 垃圾回收
  • 小结


引言

FastThreadLocal 是 Netty 中造的一个轮子,那么为什么放着好端端的ThreadLocal不用,却要重复造轮子呢?下面是Netty官方在源码注释中给出的解释:

  • FastThreadLocal是ThreadLocal的一种特殊变体,当从FastThreadLocalThread访问时可以获得更高的访问性能。
  • 内部FastThreadLocal使用数组中的常量索引来查找变量,而不是使用哈希码和哈希表来查找。尽管看似非常微小,但与使用哈希表相比,它在性能上略有优势,特别是在频繁访问时。

本文我们就来简单看看FastThreadLocal的具体实现。

在正式进入实现解析之前,下面先给出FastThreadLocal使用示例:

    private static void fastThreadLocal() {final int MAX = 100000;long start = System.currentTimeMillis();// DefaultThreadFactory是Netty提供的实现,用于创建类型为FastThreadLocalThread的线程DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(FastThreadLocalTest.class);FastThreadLocal<String>[] fastThreadLocal = new FastThreadLocal[MAX];for (int i = 0; i < MAX; i++) {fastThreadLocal[i] = new FastThreadLocal<>();}// 测试单线程读写FastThreadLocal的耗时Thread thread = defaultThreadFactory.newThread(() -> {for (int i = 0; i < MAX; i++) {fastThreadLocal[i].set("java: " + i);}System.out.println("fastThreadLocal set: " + (System.currentTimeMillis() - start));for (int i = 0; i < MAX; i++) {for (int j = 0; j < MAX; j++) {fastThreadLocal[i].get();}}});thread.start();try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("fastThreadLocal total: " + (System.currentTimeMillis() - start));}

FastThreadLocal

在这里插入图片描述
整体来看,FastThreadLocal的整体结构和ThreadLocal是一致的,唯一的区别在于InternalThreadLocalMap 内部存储上,ThreadLocalMap 采用哈希定位实现,而InternalThreadLocalMap 采用数组常量索引实现,即:

  • 每个FastThreadLocal与一个固定的数字常量相关联。

在这里插入图片描述
FastThreadLocal内部都会保存一个index下标,该下标在FastThreadLocal实例初始化的时候被赋值:

public class FastThreadLocal<V> {// index 被final修饰,确保FastThreadLocal在InternalThreadLocalMap数组中的下标是固定不变的private final int index;public FastThreadLocal() {// 计数器不断递增index = InternalThreadLocalMap.nextVariableIndex();}...
}    

还有一点也很重要,InternalThreadLocalMap内部使用的桶数组没有采用弱引用实现,而是普通的强引用:

    // 1. InternalThreadLocalMap中桶数组的实现private Object[] indexedVariables;// 2. ThreadLocalMap中桶数组的实现static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private Entry[] table;

大家可以思考,InternalThreadLocalMap此处不使用弱引用实现,是否存在内存泄漏问题 ? 即当用户程序本身失去了对FastThreadLocal实例的强引用后,仍然被InternalThreadLocalMap强引用的FastThreadLocal如何被回收掉呢?

这里需要注意一点: InternalThreadLocalMap与ThreadLocalMap没有继承关系
在这里插入图片描述


set

当我们通过FastThreadLocal的set方法设置值时,其实和ThreadLocal一样,还是向InternalThreadLocalMap中设置值:

    public final void set(V value) {// 1. UNSET 是空桶标记-->等价于ThreadLocal中被垃圾回收后key为null的空Entry if (value != InternalThreadLocalMap.UNSET) {// 2. 获取与当前线程关联的InternalThreadLocalMap// 以FastThreadLocal为key,value为val设置到InternalThreadLocalMap中    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();setKnownNotUnset(threadLocalMap, value);} else {// 3. 当设置的值为UNSET时,表明需要清空当前FastThreadLocalremove();}}

关于Set的整个流程,有两点值得我们思考:

如何获取当前线程私有的InternalThreadLocalMap ?

如果我们当前使用的线程类型为FastThreadLocalThread,那么可以直接获取FastThreadLocalThread内部持有的InternalThreadLocalMap:

public class FastThreadLocalThread extends Thread {...// 这一点和Thread内部保存ThreadLocalMap实现一致private InternalThreadLocalMap threadLocalMap;...
}

如果我们当前使用的线程类型是原始类型Thread,那么Netty这里会将InternalThreadLocalMap保存于当前线程私有的ThreadLocal内部:

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =new ThreadLocal<InternalThreadLocalMap>();...
}            

上面两种获取方式,前一种被称为fastGet,而后一种被称为slowGet :

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public static InternalThreadLocalMap get() {Thread thread = Thread.currentThread();if (thread instanceof FastThreadLocalThread) {return fastGet((FastThreadLocalThread) thread);} else {return slowGet();}}// 1. 当前线程类型为FastThreadLocalThread,则直接从获取其内部持有的InternalThreadLocalMap实例private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();if (threadLocalMap == null) {thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());}return threadLocalMap;}// 2. 当前线程类型为传统的Thread类型,则从当前线程私有的ThreadLocal中获取InternalThreadLocalMap实例 private static InternalThreadLocalMap slowGet() {InternalThreadLocalMap ret = slowThreadLocalMap.get();if (ret == null) {ret = new InternalThreadLocalMap();slowThreadLocalMap.set(ret);}return ret;}  ...
}  

如何知道当前线程使用到了哪些FastThreadLocal实例 ?

为什么FastThreadLocal需要获取到当前线程使用到的所有FastThreadLocal实例呢?

上面说过,InternalThreadLocalMap本身没有采用弱引用实现,那么Netty就需要另想办法回收掉失去了用户程序强引用的FastThreadLocal,防止产生内存泄漏。Netty此处采用的方式就是在FastThreadLocalRunnable包装的Runnable对象任务执行完毕后,清理掉当前线程使用到的所有FastThreadLocal实现的:

final class FastThreadLocalRunnable implements Runnable {private final Runnable runnable;private FastThreadLocalRunnable(Runnable runnable) {this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");}@Overridepublic void run() {try {runnable.run();} finally {FastThreadLocal.removeAll();}}static Runnable wrap(Runnable runnable) {return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);}
}

那这里还是回归第二个问题本身,即如何获取当前线程使用到的所有FastThreadLocal实例呢?

public class FastThreadLocal<V> {private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {// 1. 尝试向threadLocalMap中设置值,如果是第一次设置,则记录当前线程使用到了当前ThreadLocal// (直接常量值定位FastThreadLocal在ThreadLocalMap的哪个槽中) if (threadLocalMap.setIndexedVariable(index, value)) {// 2. 记录当前线程使用到了当前FastThreadLocaladdToVariablesToRemove(threadLocalMap, this);}}private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {// 1. variablesToRemoveIndex固定为0,threadLocalMap数组第一个槽位存放当前线程使用到的FastThreadLocal集合Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);Set<FastThreadLocal<?>> variablesToRemove;// 2. 说明当前FastThreadLocal是当前线程第一个使用到的FastThreadLocal实例if (v == InternalThreadLocalMap.UNSET || v == null) {// 3. 准备一个Set集合variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());// 4. threadLocalMap中的0号槽位固定存放当前线程使用到的FastThreadLocal实例threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);} else {variablesToRemove = (Set<FastThreadLocal<?>>) v;}// 5. 记录当前FastThreadLocal到集合中去variablesToRemove.add(variable);}...
}public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public boolean setIndexedVariable(int index, Object value) {Object[] lookup = indexedVariables;// 1. 判断InternalThreadLocalMap是否装满if (index < lookup.length) {Object oldValue = lookup[index];lookup[index] = value;// 2. 如果当前槽位先前为空,说明是第一次使用到当前FastThreadLocalreturn oldValue == UNSET;} else {// 3. 执行扩容,扩容完毕后,在设置进去 --> 说明当前FastThreadLocal是第一次被使用expandIndexedVariableTableAndSet(index, value);return true;}}....
}    

当前线程会在第一次使用到某个FastThreadLocal时进行记录,使用到的FastThreadLocal集合保存在InternalThreadLocalMap数组的0号槽位中:

public class FastThreadLocal<V> {// 当FastThreadLocal类本身执行初始化时,该下标就被初始化了,值默认为0private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();...
}public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {// 这里的计数器也是全局共享的private static final AtomicInteger nextIndex = new AtomicInteger();...public static int nextVariableIndex() { // 每次获取下标时,计数器累加一位int index = nextIndex.getAndIncrement();...return index;}
}    

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

set的整个流程中,我们也可以看出FastThreadLocal快就快在,可以根据当前FastThreadLocal实例关联的常量值直接定位其在InternalThreadLocalMap中的位置。


get

FastThreadLocal get的流程很简单,如下所示:

public class FastThreadLocal<V> {public final V get(InternalThreadLocalMap threadLocalMap) {// 1. 直接常量定位所在槽位Object v = threadLocalMap.indexedVariable(index);// 2. 如果当前FastThreadLocal并非首次访问,则直接对应的值if (v != InternalThreadLocalMap.UNSET) {return (V) v;}// 3. 初始化FastThreadLocalreturn initialize(threadLocalMap);}private V initialize(InternalThreadLocalMap threadLocalMap) {V v = null;try {// 1. 调用回调进行初始化v = initialValue();} catch (Exception e) {PlatformDependent.throwException(e);}// 2. 设置初始化的值threadLocalMap.setIndexedVariable(index, v);// 3. 注册当前FastThreadLocal,即记录当前线程使用了当前FastThreadLocal实例addToVariablesToRemove(threadLocalMap, this);return v;}...
}    

垃圾回收

上面说过,InternalThreadLocalMap本身没有采用弱引用实现,那么Netty就需要另想办法回收掉失去了用户程序强引用的FastThreadLocal,防止产生内存泄漏。Netty此处采用的方式就是在FastThreadLocalRunnable包装的Runnable对象任务执行完毕后,清理掉当前线程使用到的所有FastThreadLocal实现的,这一点上面已经提到过了,下面我们看看具体实现。

final class FastThreadLocalRunnable implements Runnable {private final Runnable runnable;private FastThreadLocalRunnable(Runnable runnable) {this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");}@Overridepublic void run() {try {runnable.run();} finally {FastThreadLocal.removeAll();}}static Runnable wrap(Runnable runnable) {return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);}
}

FastThreadLocal提供了一个静态的removeAll方法,用于清除当前线程使用到的所有FastThreadLocal实例:

public class FastThreadLocal<V> {... public static void removeAll() {// 1. 如果当前线程没有使用到FastThreadLocal,这里直接返回InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();if (threadLocalMap == null) {return;}try {// 2. 获取固定的0号槽位保存的Set集合,该集合内保存了当前线程使用到的所有FastThreadLocal实例集合 Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);if (v != null && v != InternalThreadLocalMap.UNSET) {Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;// 3. 遍历该集合内每个FastThreadLocal实例,依次调用remove方法 FastThreadLocal<?>[] variablesToRemoveArray =variablesToRemove.toArray(new FastThreadLocal[0]);for (FastThreadLocal<?> tlv: variablesToRemoveArray) {tlv.remove(threadLocalMap);}}} finally {// 4. 置空threadlocalmapInternalThreadLocalMap.remove();}}
  1. 清空单个FastThreadLocal
public class FastThreadLocal<V> {public final void remove(InternalThreadLocalMap threadLocalMap) {if (threadLocalMap == null) {return;}// 1. 清除当前FastThreadLocal占用的槽位Object v = threadLocalMap.removeIndexedVariable(index);// 2. 取消当前FastThreadLocal的注册removeFromVariablesToRemove(threadLocalMap, this);// 3. 执行回调通知  if (v != InternalThreadLocalMap.UNSET) {try {onRemoval((V) v);} catch (Exception e) {PlatformDependent.throwException(e);}}}private static void removeFromVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {// 1. 获取threadlocalmap的0号槽位保存的set集合  Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);...// 2. 从set集合中移除当前fastThreadLocalSet<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;variablesToRemove.remove(variable);}...
}public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public Object removeIndexedVariable(int index) {Object[] lookup = indexedVariables;if (index < lookup.length) {Object v = lookup[index];// 将对应槽位设置为UNSETlookup[index] = UNSET;return v;} else {return UNSET;}}...
}
  1. 置空ThreadLocalMap
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public static void remove() {Thread thread = Thread.currentThread();// 1. 如果threadLocalMap保存在FastThreadLocalThread内部,则直接设置为nullif (thread instanceof FastThreadLocalThread) {((FastThreadLocalThread) thread).setThreadLocalMap(null);} else {// 2. 如果保存在当前线程threadlocal中,则调用threadlocal的remove方法移除 slowThreadLocalMap.remove();}}...
} 

小结

FastThreadLocal为什么那么快,这个问题比较好回答:

  1. FastThreadLocal 内部维护了一个索引常量 index,该常量在每次创建 FastThreadLocal 中都会自动+1,从而保证了下标的不重复性。
  2. 这要做虽然会产生大量的 index,但避免了在 ThreadLocal 中计算索引下标位置以及处理 hash 冲突带来的损耗,所以在操作数组时使用固定下标要比使用计算哈希下标有一定的性能优势,特别是在频繁使用时会非常显著,用空间换时间,这就是高性能 Netty 的巧妙之处。
  3. 要利用 FastThreadLocal 带来的性能优势,就必须结合使用 FastThreadLocalThread 线程类或其子类,因为 FastThreadLocalThread 线程类会存储必要的状态,如果使用了非 FastThreadLocalThread 线程类则会回到常规 ThreadLocal。

下面给出一个测试用例,来看看FastThreadLocal和ThreadLocal在性能上的差异:

public class FastThreadLocalTest {public static void main(String[] args) {new Thread(FastThreadLocalTest::threadLocal).start();new Thread(FastThreadLocalTest::fastThreadLocal).start();}private static void fastThreadLocal() {final int MAX = 100000;long start = System.currentTimeMillis();// DefaultThreadFactory是Netty提供的实现,用于创建类型为FastThreadLocalThread的线程DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(FastThreadLocalTest.class);FastThreadLocal<String>[] fastThreadLocal = new FastThreadLocal[MAX];for (int i = 0; i < MAX; i++) {fastThreadLocal[i] = new FastThreadLocal<>();}// 测试单线程读写FastThreadLocal的耗时Thread thread = defaultThreadFactory.newThread(() -> {for (int i = 0; i < MAX; i++) {fastThreadLocal[i].set("java: " + i);}System.out.println("fastThreadLocal set: " + (System.currentTimeMillis() - start));for (int i = 0; i < MAX; i++) {for (int j = 0; j < MAX; j++) {fastThreadLocal[i].get();}}});thread.start();try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("fastThreadLocal total: " + (System.currentTimeMillis() - start));}private static void threadLocal() {final int MAX = 100000;long start = System.currentTimeMillis();ThreadLocal<String>[] threadLocals = new ThreadLocal[MAX];for (int i = 0; i < MAX; i++) {threadLocals[i] = new ThreadLocal<>();}Thread thread = new Thread(() -> {for (int i = 0; i < MAX; i++) {threadLocals[i].set("java: " + i);}System.out.println("threadLocal set: " + (System.currentTimeMillis() - start));for (int i = 0; i < MAX; i++) {for (int j = 0; j < MAX; j++) {threadLocals[i].get();}}});thread.start();try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("threadLocal total: " + (System.currentTimeMillis() - start));}}

在这里插入图片描述
在大量读写面前,写操作的效率差不多,但读操作 FastThreadLocal 比 ThreadLocal 快的不是一个数量级,简直是秒杀 ThreadLocal 的存在。

当我们把max的值缩小为1000时,此时读写操作不多时,ThreadLocal 明显更胜一筹!
在这里插入图片描述
Netty 中的 FastThreadLocal 在大量频繁读写操作时效率要高于 ThreadLocal,但要注意结合 Netty 自带的线程类使用。

如果没有大量频繁读写操作的场景,JDK 自带的 ThreadLocal 足矣,并且性能还要优于 FastThreadLocal。

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

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

相关文章

一篇理解网络分层原理

一、网络分层的必要性。 如图是一个数据的传输过程&#xff0c;在这个途中会有很多的原因导致数据丢失&#xff0c;网络分层就要可以很大程度的避免这个现象。 网络分层的必要性体现在以下几个方面&#xff1a; 抽象复杂度&#xff1a;网络分层将网络功能按照不同的层次进行分…

SQL进阶 - SQL的编程规范

性能优化是一个很有趣的探索方向&#xff0c;将耗时耗资源的查询优化下来也是一件很有成就感的事情&#xff0c;但既然编程是一种沟通手段&#xff0c;那每一个数据开发者就都有义务保证写出的代码逻辑清晰&#xff0c;具有很好的可读性。 目录 引子 小试牛刀 答案 引言 …

IDEA 2021.2.2设置自动热部署

1.导入包坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency> 2.pom.xml添加piugins插…

车载网络诊断应如何测试?

文章目录 一、前言二、测试内容三、测试设备和台架方案四、测试脚本及工程五、其他一、前言 目前车上主流的网络有CAN、LIN、ETH(以太网)。 按照测试环境可以划分为单件测试,系统测试,整车测试。 我们来看下CAN和以太网的分层图: CAN的分层图: 以太网的分层图: 最好的…

【C++】STL详解(十二)—— 用哈希表封装出unordered_map和unordered_set

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】STL…

基于Dockerfile搭建LNMP

目录 一、基础环境准备 1、环境前期准备 二、部署nginx&#xff08;容器IP 为 172.18.0.10&#xff09; 1、配置Dockerfile文件 2、配置nginx.conf文件 3、构建镜像、启动镜像 三、部署mysql 1、配置Dockerfile文件 2、配置my.conf文件 3、构建镜像、启动镜像 5、验…

经典算法----迷宫问题(找出所有路径)

目录 前言 问题描述 算法思路 定义方向 回溯算法 代码实现 前言 前面我发布了一篇关于迷宫问题的解决方法&#xff0c;是通过栈的方式来解决这个问题的&#xff08;链接&#xff1a;经典算法-----迷宫问题&#xff08;栈的应用&#xff09;-CSDN博客&#xff09;&#xff…

One Thread One Loop主从Reactor模型⾼并发服务器

One Thread One Loop主从Reactor模型⾼并发服务器 文章目录 One Thread One Loop主从Reactor模型⾼并发服务器一些补充HTTP服务器Reactor 模型eventfd通用类Any 目标功能模块划分&#xff1a;SERVER模块Buffer模块&#xff1a;编写思路&#xff1a;接口设计&#xff1a;具体实现…

详谈Spring

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 目录 一、Spring是什么&#xff1f; 1.1 Spring框架的一些核心特点&#xff1a; 二、IoC&#xff08;控制反转&#xff09;是什么&#xff1f; 2.1 实现手段 2.2 依赖注入&#xff08;DI&#xff09;的实现原理 2.3 优点 三、AO…

Springboot学习笔记——2

Springboot学习笔记——2 一、打包与运行1.1、程序打包与运行&#xff08;windows版&#xff09;1.2、打包插件1.3、Boot工程快速启动&#xff08;Linux版&#xff09; 二、配置高级2.1、临时属性设置2.2、配置程序四级分类2.3、自定义配置文件 三、多环境开发3.1、多环境开发&…

深圳市重点实验室申报条件-华夏泰科

深圳市重点实验室是一个致力于科学研究和技术创新的重要机构。作为中国科技创新的重要一环&#xff0c;深圳市重点实验室在多个领域展开前沿研究&#xff0c;并为科学家、工程师和创新者提供了宝贵的资源和支持。、在接下来的内容中&#xff0c;华夏泰科将为您说明深圳市重点实…

强化学习------Sarsa算法

简介 SARSA&#xff08;State-Action-Reward-State-Action&#xff09;是一个学习马尔可夫决策过程策略的算法&#xff0c;通常应用于机器学习和强化学习学习领域中。它由Rummery 和 Niranjan在技术论文“Modified Connectionist Q-Learning&#xff08;MCQL&#xff09;” 中…

SpringTask ----定时任务框架 ----苍穹外卖day10

目录 SpringTask 需求分析 快速入门 使用步骤 ​编辑业务开发 SpringTask 定时任务场景特化的框架 需求分析 快速入门 使用cron表达式来使用该框架 使用步骤 添加注解 自定义定时任务类 重点在于以下cron表达式的书写,精确表达触发的间隔 业务开发 主task方法 time使用(-…

数据结构:二叉树(超详解析)

目录​​​​​​​ 1.树概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 1.3.1孩子兄弟表示法&#xff1a; 1.3.2双亲表示法&#xff1a;只存储双亲的下标或指针 两节点不在同一树上&#xff1a; 2.二叉树概念及结构 2.1.概念 2.2.特殊的二叉树&#xff1a; 2…

【C++设计模式之组合模式:结构型】分析及示例

简介 组合模式是一种结构型设计模式&#xff0c;它能够将对象组合成树形结构以表示“整体-部分”的层次结构&#xff0c;并且能够使用相同的方式处理单个对象和组合对象。组合模式使得客户端可以一致地处理单个对象和组合对象&#xff0c;无需关心具体的对象类型。 组合模式将对…

企业想过等保,其中2FA双因素认证手段必不可少

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显。等保2.0时代的到来&#xff0c;意味着企业和组织需要更加严格地保护自身的信息安全。而在这个过程中&#xff0c;双因素认证的重要性逐渐得到广泛认可。本文将探讨 2FA 双因素认证的重要性。 在了解 2FA 双因素认证的…

2023-IDEA插件推荐

CamelCase 链接 https://plugins.jetbrains.com/plugin/7160-camelcase https://github.com/netnexus/camelcaseplugin 介绍 提供下划线、驼峰等代码风格的切换。快捷键是⇧ ⌥ U / Shift Alt U GsonFormatPlus 链接 https://plugins.jetbrains.com/plugin/14949-gs…

2023/10/7 -- ARM

【程序状态寄存器读写指令】 1.指令码以及格式 mrs:读取CPSR寄存器的值 mrs 目标寄存器 CPSR&#xff1a;读取CPSR的数值保存到目标寄存器中msr:修改CPSR寄存器的数值msr CPSR,第一操作数:将第一操作数的数值保存到CPSR寄存器中//修改CPSR寄存器&#xff0c;也就表示程序的状…

从哈希表到红黑树:探讨 epoll 是如何管理事件的?

一、引言 在计算机领域&#xff0c;事件通知是一种重要的机制&#xff0c;用于监视和响应各种事件&#xff0c;例如网络连接、文件IO、定时器等。随着计算机应用变得越来越复杂&#xff0c;对于高性能事件通知机制的需求也越来越迫切。传统的事件通知机制可能存在效率低下的问…

Excel·VBA使用ADO读取工作簿工作表数据

目录 查询遍历写入数组查询整体写入数组查询工作簿所有工作表名称查询工作簿所有工作表数据 不打开工作簿读取数据&#xff0c;以下举例都为《ExcelVBA合并工作簿》中 7&#xff0c;合并子文件夹同名工作簿中同名工作表&#xff0c;纵向汇总数据所举例的工作簿&#xff0c;使用…