当前位置: 首页 > news >正文

研二学妹面试字节,竟倒在了ThreadLocal上,这是不要应届生还是不要女生啊?

一、写在开头

    今天和一个之前研二的学妹聊天,聊及她上周面试字节的情况,着实感受到了Java后端现在找工作的压力啊,记得在18,19年的时候,研究生计算机专业的学生,背背八股文找个Java开发工作毫无问题,但现在即便你是应届生,问的考题也非常的深入和细节了,只会背八股,没有一定的代码量和项目积累,根本找不到像样的工作,具体聊天内容如下:

在这里插入图片描述
既然大厂的面试都拷问到ThreadLocal了,那今天build哥就花点时间也来温习一下这个知识点吧,尽可能整理的细致一点!🤓🤓

二、ThreadLocal简介

2.1 ThreadLocal的作用

处理并发编程的时候,其核心问题是当多个线程去访问共享变量时,因为顺序、资源分配等原因带来了数据的不准确,我们叫这种情况为线程不安全,为了解决线程安全问题,在Java中可以采用Lock、 synchronzed关键字等方式,但这种方式对于没有持有锁的线程来说会阻塞,这样以来在时间性能上就有所损失。

为了解决这个问题,Java的lang包中诞生出了一个类,名为 ThreadLocal,见名知意,它被视为线程的“本地变量”,主要用来存储各线程的私有数据,当多个线程访问同一个ThreadLocal变量时,实际上它们访问的是各自线程本地存储的副本,而不是共享变量本身。因此,每个线程都可以独立地修改自己的副本,而不会影响到其他线程。这种以空间换时间的方式,可以大大的提升处理时间。

2.2 ThreadLocal的使用案例

上面了解了它的特性后,我们来写一个小demo感受一下ThreadLocal的使用。

public class TestService implements Runnable{// SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本//共享变量private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));public static void main(String[] args) throws InterruptedException {TestService obj = new TestService();//循环创建5个线程for(int i=0 ; i<5; i++){Thread t = new Thread(obj, ""+i);Thread.sleep(new Random().nextInt(1000));t.start();}}@Overridepublic void run() {System.out.println("Thread:"+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());try {Thread.sleep(new Random().nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();}//formatter pattern is changed here by thread, but it won't reflect to other threads//设置副本的值formatter.set(new SimpleDateFormat());System.out.println("Thread:"+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());}
}

输出:

Thread:0 default Formatter = yyyyMMdd
Thread:1 default Formatter = yyyyMMdd
Thread:2 default Formatter = yyyyMMdd
Thread:1 formatter = yy-M-d ah:mm
Thread:0 formatter = yy-M-d ah:mm
Thread:3 default Formatter = yyyyMMdd
Thread:2 formatter = yy-M-d ah:mm
Thread:3 formatter = yy-M-d ah:mm
Thread:4 default Formatter = yyyyMMdd
Thread:4 formatter = yy-M-d ah:mm

从输出中可以看出,虽然 Thread-0 已经改变了 formatter 的值,但 Thread-1 默认格式化值与初始化值相同并没有被修改,其他线程也一样,这说明每个线程获取ThreadLocal变量值的时候,确访问的时线程本地的副本值。

三、ThreadLocal的实现原理

我们从Thread源码入手,一步步的跟进,去探索ThreadLocal的实现原理。首先,在Thread的源码中,我们看到了这样的两句定义语句:

public class Thread implements Runnable {//......//与此线程有关的ThreadLocal值。由ThreadLocal类维护ThreadLocal.ThreadLocalMap threadLocals = null;//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//......
}

threadLocals 、inheritableThreadLocals 都是ThreadLocalMap变量,而这个Map我们可以看作是ThreadLocal的定制化HashMap,用来存储线程本地变量的容器,是一个静态内部类,而这两个变量的值初始为null,只有当前线程调用 ThreadLocal 类的 set或get方法时才创建它们,那我们继续去看set/get方法。

【set方法解析】

public void set(T value) {//1. 获取当前线程实例对象Thread t = Thread.currentThread();//2. 通过当前线程实例获取到ThreadLocalMap对象ThreadLocalMap map = getMap(t);if (map != null)//3. 如果Map不为null,则以当前ThreadLocal实例为key,值为value进行存入map.set(this, value);else//4.map为null,则新建ThreadLocalMap并存入valuecreateMap(t, value);
}

在ThreadLocal的set方法中通过getMap()方法去获取当前线程的ThreadLocalMap对象,并对获取到的map进行判断,我们跟如到getMap方法中去,发现其实里面返回的是初始化定义的threadLocals变量。

ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

在threadLocals没有被调用初始化方法重新赋值的时候,它为null(不为null时,直接set进行赋值,当前ThreadLocal实例为key,值为value),set方法中会去调用createMap(t,value)进行处理,我们继续跟入这个方法的源码去看看:

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

我们可以看到,在这个方法内部,会去新构造一个ThreadLocalMap的实例,并将value值初始化进去,并赋给threadLocals。

看完了set方法的底层实现我们知道:

  1. 最终变量存储的位置在ThreadLocalMap里,ThreadLocal可以视为这个Map的封装;
  2. 无论如何最终threadLocals存储的数据都是以线程为key,对应的局部变量为值得映射表;
  3. 因为映射表的原因,确保了每个线程的局部变量都时独立的。

【get方法解析】

看完了set的源码,我们继续来看看get方法的底层实现吧,既然有存(set)就有取(get),get 方法提供的就是获取当前线程中 ThreadLocal 的变量值的功能!

public T get() {//1. 获取当前线程的实例对象Thread t = Thread.currentThread();//2. 获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//3. 获取map中当前ThreadLocal实例为key的值的entryThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")//4. 当前entitiy不为null的话,就返回相应的值valueT result = (T)e.value;return result;}}//5. 若map为null或者entry为null的话通过该方法初始化,并返回该方法返回的valuereturn setInitialValue();
}

我们上面提到了线程的变量值是和线程的ThreadLocal有映射关系的,所以这里将当前线程的ThreadLocal作为key去map中获取值,若map为null或者entry为null的话通过该方法初始化,并返回该方法返回的value,我们去看看setInitialValue的实现:

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}
protected T initialValue() {return null;
}

这个方法里的实现和set几乎一模一样,这里调用了一个protected访问修饰符的方法initialValue(),这个方法可以被子类重写。

我们在2.2使用案例中写道的ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));这是在Java8中的写法,等价于:

private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){@Overrideprotected SimpleDateFormat initialValue(){return new SimpleDateFormat("yyyyMMdd");}
};

setInitialValue 方法的目的是确保每个线程在第一次尝试访问其 ThreadLocal 变量时都有一个合适的值。

3.1 ThreadLocalMap

上面我们也说了,ThreadLocalMap是ThreadLocal的静态内部类,而每个线程独立的变量副本存储也是在这个Map中,它是一个定制的哈希表,底层维护了一个Entry 类型的数组类型的数组 table,它的内部提供了set、remove、getEntry等方法。

Entry静态内部类
这个Entry又是ThreadLocalMap的一个静态内部类,我们看一下它的源码:

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

Entry 继承了弱引用 WeakReference<ThreadLocal<?>>,它的 value 字段用于存储与特定 ThreadLocal 对象关联的值,key 为弱引用,意味着当 ThreadLocal 外部强引用被置为 null(ThreadLocalInstance=null)时,根据可达性分析,ThreadLocal 实例此时没有任何一条链路引用它,所以系统 GC 的时候 ThreadLocal 会被回收。这种操作看似利用垃圾回收器节省了内存空间,实则存在一个风险,也就是我们下面要说的内存泄露问题!

只具有弱引用的对象,拥有更为短暂的生命周期,在GC线程扫描到它所在的内存区域的时候,一旦发现了只有弱引用的对象的时候,不管内存够不够用都会将其回收掉

四、ThreadLocal内存泄漏问题

4.1 内存泄漏的原因

如果非要问ThreadLocal有什么缺点的话,那就是使用不当的时候,会带来内存泄漏问题。

内存泄漏 是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

根据3.1中的分析,我们知道ThreadLocalMap中的使用的key是ThreadLocal的弱引用,Value为强引用,如果ThreadLocal没有被强引用的话,key会被GC掉,而value依旧存在,若我们采用任何措施的前提下,线程一直运行,那这些value值就会一直存在,过多的占用内存,导致内存泄漏!

4.2 如何解决内存泄漏

如何解决内存泄漏呢,只需要记得在使用完 ThreadLocal 中存储的内容后将它 remove 掉就可以了。

//ThreadLocal提供的清理方法
public void remove() {//1. 获取当前线程的ThreadLocalMapThreadLocalMap m = getMap(Thread.currentThread());if (m != null)//2. 从map中删除以当前ThreadLocal实例为key的键值对m.remove(this);
}
/*** ThreadLocalMap中的remove方法*/
private void remove(ThreadLocal<?> key) {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)]) {if (e.get() == key) {//将entry的key置为nulle.clear();//将该entry的value也置为nullexpungeStaleEntry(i);return;}}
}

除此之外,我们还可以使用Java 8引入的InheritableThreadLocal来替代ThreadLocal,它可以在子线程中自动继承父线程的线程局部变量值,从而避免在创建新线程时重复设置值的问题。但是同样需要注意及时清理资源以避免内存泄漏。

五、线程间局部变量传值问题

上面我们提到的Java8中引入的InheritableThreadLocal类,这是实现父子线程间局部变量传值的关键!
InheritableThreadLocal存在于java.lang包中是ThreadLocal的扩展,它有一个特性,那就是当创建一个新的线程时,如果父线程中有一个 InheritableThreadLocal 变量,那么子线程将会继承这个变量的值。这意味着子线程可以访问其父线程为此类变量设置的值。我们写一个小demo感受一下!

public class TestService{// 创建一个 InheritableThreadLocal 变量private static final InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {// 在主线程中设置值inheritableThreadLocal.set("这是父线程的值");System.out.println("父线程中的值: " + inheritableThreadLocal.get());// 创建一个子线程Thread childThread = new Thread(() -> {// 在子线程中尝试获取值,由于使用了 InheritableThreadLocal,这里会获取到父线程中设置的值System.out.println("子线程中的值: " + inheritableThreadLocal.get());});// 启动子线程childThread.start();// 等待子线程执行完成try {childThread.join();} catch (InterruptedException e) {e.printStackTrace();}// 主线程结束时清除值,防止潜在的内存泄漏inheritableThreadLocal.remove();}
}

输出:

父线程中的值: 这是父线程的值
子线程中的值: 这是父线程的值

输出不出所料,在子线程中获取的其实是父线程设置的inheritableThreadLocal值。

5.1 父子线程局部变量传值的实现原理

我们看到上面的输出后,应该思考这样的一个问题:子线程是怎么拿到父线程的inheritableThreadLocal值得呢?其实要从子线程的初始化开始说起,在线程Thread的内部,有着这样的一个初始化方法:

private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,// 该参数一般默认是 trueboolean inheritThreadLocals) {// 省略大部分代码Thread parent = currentThread();// 复制父线程的 inheritableThreadLocals 属性,实现父子线程局部变量共享if (inheritThreadLocals && parent.inheritableThreadLocals != null) {this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }// 省略部分代码
}

在这里将父线程的inheritableThreadLocals赋值了进来,我们跟入createInheritedMap方法中继续解析:

// 返回一个ThreadLocalMap,传值为父线程的
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}
//ThreadLoaclMap构建的过程中会调用该构造方法
private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];// 一个个复制父线程 ThreadLocalMap 中的数据for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {// childValue 方法调用的是 InheritableThreadLocal#childValue(T parentValue)Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}
}

在这个构造方法中,我们终于看到了InheritableThreadLocal的身影,childValue()方法就是其中的一个方法,用来给子线程赋父线程的inheritableThreadLocals值;其实InheritableThreadLocal的源码非常非常的简单,大部分的实现都取自父类ThreadLocal。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}

六、总结

OK,基于学妹在字节面试的考点,我们又梳理了一遍ThreadLocal,这个类大家还是要好好学一学的,毕竟在日后的工作中,我们肯定会使用到,譬如用它来保存用户登录信息,这样在同一个线程中的任何地方都可以获取到登录信息;用于保存事务上下文,这样在同一个线程中的任何地方都可以获取到事务上下文等等。

七、结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!
在这里插入图片描述
如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!
在这里插入图片描述

相关文章:

研二学妹面试字节,竟倒在了ThreadLocal上,这是不要应届生还是不要女生啊?

一、写在开头 今天和一个之前研二的学妹聊天&#xff0c;聊及她上周面试字节的情况&#xff0c;着实感受到了Java后端现在找工作的压力啊&#xff0c;记得在18&#xff0c;19年的时候&#xff0c;研究生计算机专业的学生&#xff0c;背背八股文找个Java开发工作毫无问题&#x…...

Golang:gammazero/deque是一个快速环形缓冲区deque(双端队列)实现

gammazero/deque是一个快速环形缓冲区deque&#xff08;双端队列&#xff09;实现。 文档 https://github.com/gammazero/deque 安装 go get github.com/gammazero/deque代码示例 先入先出队列 package mainimport ("fmt""github.com/gammazero/deque&quo…...

C++ 时间处理-统计函数运行时间

1. 关键词2. 问题3. 解决思路4. 代码实现 4.1. timecount.h4.2. timecount.cpp 5. 测试代码6. 运行结果7. 源码地址 1. 关键词 C 时间处理 统计函数运行时间 跨平台 2. 问题 C如何简单便捷地实现“函数运行时间的统计”功能&#xff1f; 3. 解决思路 类的构造函数&#x…...

JAVA面试题大全(十五)

1、Zookeeper 是什么&#xff1f; zookper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务。是 google chubby 的开源实现&#xff0c;是 hadoop 和 hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维护…...

使用python对指定文件夹下的pdf文件进行合并

使用python对指定文件夹下的pdf文件进行合并 介绍效果代码 介绍 对指定文件夹下的所有pdf文件进行合并成一个pdf文件。 效果 要合并的pdf文件&#xff0c;共计16个1页的pdf文件。 合并成功的pdf文件&#xff1a;一个16页的pdf文件。 代码 import os from PyPDF2 import …...

Day50 | 309.最佳买卖股票时机含冷冻期 714.买卖股票的最佳时机含手续费 总结

代码随想录算法训练营Day50 | 309.最佳买卖股票时机含冷冻期 714.买卖股票的最佳时机含手续费 总结 LeetCode 309.最佳买卖股票时机含冷冻期 题目链接&#xff1a;LeetCode 309.最佳买卖股票时机含冷冻期 思路&#xff1a; 四个状态。 保持持有股票&#xff0c;保持卖出股票…...

Steam在连接至服务器发生错误/连接服务器遇到问题解决办法

Steam作为全球最大的数字游戏分发平台&#xff0c;构建了一个活跃的玩家社区&#xff0c;用户可以创建个人资料&#xff0c;添加好友&#xff0c;组建群组&#xff0c;参与讨论&#xff0c;甚至直播自己的游戏过程。通过创意工坊&#xff0c;玩家还能分享自制的游戏模组、地图、…...

kafka 工作流程文件存储

爬虫组件分析 目录概述需求&#xff1a; 设计思路实现思路分析1.kafka 工作流程2.kafka 文件存储 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for…...

贪心算法4(c++)

过河的最短时间 题目描述 输入 在漆黑的夜里&#xff0c;N位旅行者来到了一座狭窄而且没有护栏的桥边。如果不借助手电筒的话&#xff0c;大家是无论如何也不敢过桥去的。不幸的是&#xff0c;N个人一共只带了一只手电筒&#xff0c;而桥窄得只够让两个人同时过&#xff0c;如果…...

【无标题】yoloV8目标检测与实例分割--目标检测onnx模型部署

1. 模型转换 ONNX Runtime 是一个开源的高性能推理引擎&#xff0c;用于部署和运行机器学习模型&#xff0c;其设计的目标是优化执行open neural network exchange &#xff08;onnx&#xff09;格式定义各模型&#xff0c;onnx是一种用于表示机器学习模型的开放标准。ONNX Ru…...

深入理解与防御跨站脚本攻击(XSS):从搭建实验环境到实战演练的全面教程

跨站脚本攻击&#xff08;XSS&#xff09;是一种常见的网络攻击手段&#xff0c;它允许攻击者在受害者的浏览器中执行恶意脚本。以下是一个XSS攻击的实操教程&#xff0c;包括搭建实验环境、编写测试程序代码、挖掘和攻击XSS漏洞的步骤。 搭建实验环境 1. 安装DVWA&#xff…...

初步认识栈和队列

Hello&#xff0c;everyone&#xff0c;今天小编讲解栈和队列的知识&#xff01;&#xff01;&#xff01; 1.栈 1.1栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶&…...

插件:NGUI

一、版本 安装完毕后重启一下即可&#xff0c;否则可能创建的UI元素不生效 二、使用 Label文字 1、创建Canvs 2、只有根节点的这些脚本全部展开才能鼠标右键创建UI元素 3、选择字体 Label添加打字效果 Sprite图片 1、选择图集 2、选择图集中的精灵 InvisibleWidget容器 用来…...

网络爬虫原理及其应用

你是否想知道Google 和 Bing 等搜索引擎如何收集搜索结果中显示的所有数据。这是因为搜索引擎对其档案中的所有页面建立索引&#xff0c;以便它们可以根据查询返回最相关的结果。网络爬虫使搜索引擎能够处理这个过程。 本文重点介绍了网络爬虫的重要方面、网络爬虫为何重要、其…...

串口中断原理及实现

一、串口的原理 SM0、SM1——串行口工作模式 SM0SM1模式特点00模式0移位寄存器方式&#xff0c;用于I/O口扩展01模式18位UART,波特率可变10模式29位UART,波特率为时钟频率/32或/6411模式39位UART,波特率可变 TI、RI——发送、接收中断标志位 TITI0 允许发送>TI1 发送完成后…...

课时136:变量进阶_变量实践_高级赋值

2 变量进阶 2.1 变量实践 2.1.1 高级赋值 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习 基础知识 简介 所谓的高级赋值&#xff0c;是另外的一种变量值获取方法&#xff0c;这里涉及到更多我们学习之外的一些shell内置变量格式,其实这部分…...

牛客网刷题 | BC99 正方形图案

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 KiKi学习了循环&am…...

启动小程序F12窗口管理器

如何使用小程序F12任务窗口管理器教学流程 一、引言 小程序的开发者们&#xff0c;是否希望有一款工具能帮助你们更好地管理任务窗口&#xff1f; 二、前置准备 观看视频教程 访问B站视频链接&#xff1a;https://www.bilibili.com/video/BV1aa4y197UU/?spm_id_from333.9…...

完全背包之零钱兑换I

上次分享完完全背包问题的解决思路后&#xff0c;这次分享一道和完全背包有关的leetcode题。 零钱兑换 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数。如果…...

Flutter 中的 FittedBox 小部件:全面指南

Flutter 中的 FittedBox 小部件&#xff1a;全面指南 在Flutter的丰富布局小部件中&#xff0c;FittedBox扮演着一个独特而重要的角色。它是一个灵活的组件&#xff0c;用于将子组件的大小和位置适应到给定的约束条件中。本文将提供FittedBox的全面指南&#xff0c;帮助你了解…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

webpack面试题

面试题&#xff1a;webpack介绍和简单使用 一、webpack&#xff08;模块化打包工具&#xff09;1. webpack是把项目当作一个整体&#xff0c;通过给定的一个主文件&#xff0c;webpack将从这个主文件开始找到你项目当中的所有依赖文件&#xff0c;使用loaders来处理它们&#x…...