Java 并发编程:线程变量 ThreadLocal
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 029 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。
–
在并发编程中,线程安全性始终是开发者关注的重点。为了避免多个线程对同一共享变量的竞争,通常需要复杂的同步机制。然而,
ThreadLocal
提供了一种更为简洁的解决方案,它通过为每个线程提供独立的变量副本,避免了线程间的共享状态,极大简化了并发编程中的数据管理。本文将探讨ThreadLocal
的使用方法、底层实现原理,以及其在实际开发中的应用场景和潜在的内存泄漏问题。通过对ThreadLocal
的深入理解,读者将能够更有效地管理线程中的数据,提高并发程序的安全性与性能。
文章目录
- 1、ThreadLocal 简介
- 2、ThreadLocal 的使用
- 2.1、创建方式
- 2.2、常用方法
- 2.3、Demo
- 3、ThreadLocal 原理
- 3.1、ThreadLocal 原理概述
- 3.2、ThreadLocalMap
- 3.3、ThreadLocal 相关源码解析
- 4、ThreadLocal 内存泄漏问题
- 4.1、ThreadLocal 内存泄漏问题发生的原因
- 4.2、为什么使用弱引用
- 4.3、ThreadLocal 最佳实践
- 5、Thread 相关知识点
- 5.1、关于 ThreadLocal 和 Synchronized 的区别
- 5.2、关于 ThreadLocalMap 中的 Hash 冲突处理
1、ThreadLocal 简介
ThreadLocal
即线程变量,是 Java 提供的用于实现线程本地变量的工具类。每个线程可以通过 ThreadLocal
对象访问其专属的变量,避免了多线程环境下变量共享导致的数据不一致问题。
通常情况下,我们创建的成员变量都是线程不安全的。因为他可能被多个线程同时修改,此变量对于多个线程之间彼此并不独立,是共享变量。而 ThreadLocal
中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
Ps:ThreadLocal 很容易让人望文生义,想当然地认为是一个 “本地线程”。其实,
ThreadLocal
并不是一个 Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable 更容易让人理解一些。
ThreadLocal
为变量在每个线程中都创建了一个属于当前 Thread
的副本,且该副本只能由当前 Thread
使用,其它 Thread
不可访问,因此也就不存在多线程间共享的问题了。
ThreadLocal
变量通常被 private static
修饰。当一个线程结束时,它所使用的所有 ThreadLocal
相对的实例副本都可被回收。
在适用场景上 ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
2、ThreadLocal 的使用
2.1、创建方式
通过 ThreadLocal
的构造方法创建:
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
通过带初始值的工厂方法创建:
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
2.2、常用方法
set(T value)
– 设置线程本地变量的内容。get()
– 获取线程本地变量的内容。remove()
– 移除线程本地变量(值变为 null)。Ps:在线程池的线程复用场景中在线程执行完毕时一定要调用 remove,避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。
2.3、Demo
既然 ThreadLocal 的作用是每一个线程创建一个副本,我们使用一个例子来验证一下:
public static void main(String[] args) {// 新建一个ThreadLocalThreadLocal<String> local = new ThreadLocal<>();// 新建一个随机数类Random random = new Random();// 使用 java8 的 Stream 新建 5 个线程IntStream.range(0, 5).forEach(a -> new Thread(() -> {// 为每一个线程设置相应的 local 值local.set(a + " " + random.nextInt(10));System.out.println("线程和local值分别是 " + local.get());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}).start());}
/*线程和local值分别是 0 4线程和local值分别是 2 8线程和local值分别是 3 9线程和local值分别是 1 4线程和local值分别是 4 4
*/
从结果我们可以看到,每一个线程都有各自的 local
值,我们设置了一个休眠时间,就是为了另外一个线程也能够及时的读取当前的 local
值。
3、ThreadLocal 原理
3.1、ThreadLocal 原理概述
那么如何究竟是如何实现在每个线程里面保存一份单独的本地变量呢?
实际上线程在 Java 中就是一个 Thread
类的实例对象!而 Thread
的实例对象中实例成员字段的内容肯定是这个对象独有的,所以我们也可以将保存 ThreadLocal
线程本地变量作为一个 Thread
类的成员字段,这个成员字段就是:threadLocals
。
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
每个 Thread
维护着一个 ThreadLocalMap
的引用,而 ThreadLocalMap
又是 ThreadLocal
的内部类,用 Entry
来进行存储,ThreadLocal
创建的副本是存储在自己的 threadLocals
中的,也就是自己的 ThreadLocalMap
:
-
每个
Thread
维护着一个ThreadLocalMap
的引用。 -
ThreadLocalMap
是ThreadLocal
的内部类,用Entry
来进行存储 -
ThreadLocal
创建的副本是存储在自己的threadLocals
中的,也就是自己的ThreadLocalMap
。 -
ThreadLocalMap
的键值为ThreadLocal
对象,而且可以有多个threadLocal
变量,因此保存在 map 中 -
在进行 get 之前,必须先 set,否则会报空指针异常,当然也可以初始化一个,但是必须重写 initialValue() 方法。
-
ThreadLocal
本身并不存储值,它只是作为一个 key 来让线程从ThreadLocalMap
获取 value。
3.2、ThreadLocalMap
ThreadLocalMap
是归 Thread 类所有的。它的引用在 Thread 类里,这也证实了一个问题:ThreadLocalMap
类内部为什么有 Entry 数组,而不是 Entry 对象?
因为你业务代码能 new 好多个 ThreadLocal 对象,各司其职。但是在一次请求里,也就是一个线程里,ThreadLocalMap 是同一个,而不是多个,不管你 new 几次 ThreadLocal
,ThreadLocalMap 在一个线程里就一个,因为再说一次,ThreadLocalMap 的引用是在 Thread 里的,所以它里面的 Entry 数组存放的是一个线程里你 new 出来的多个 ThreadLocal 对象。
static class ThreadLocalMap {/*** 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.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;......}
3.3、ThreadLocal 相关源码解析
ThreadLocal#set
方法的源码:
public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取当前线程的threadLocals字段ThreadLocalMap map = getMap(t);// 判断线程的threadLocals是否初始化了if (map != null) {map.set(this, value);} else {// 没有则创建一个ThreadLocalMap对象进行初始化createMap(t, value);}
}
ThreadLocal#createMap
方法的源码:
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal.ThreadLocalMap#set
方法源码:
/**
* 往map中设置ThreadLocal的关联关系
* set中没有使用像get方法中的快速选择的方法,因为在set中创建新条目和替换旧条目的内容一样常见,
* 在替换的情况下快速路径通常会失败(对官方注释的翻译)
*/
private void set(ThreadLocal<?> key, Object value) {// map中就是使用Entry[]数据保留所有的entry实例Entry[] tab = table;int len = tab.length;// 返回下一个哈希码,哈希码的产生过程与神奇的0x61c88647的数字有关int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {// 已经存在则替换旧值e.value = value;return;}if (k == null) {// 在设置期间清理哈希表为空的内容,保持哈希表的性质replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;// 扩容逻辑if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}
ThreadLocal#get
方法的源码:
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {// 获取ThreadLocal对应保留在Map中的Entry对象ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")// 获取ThreadLocal对象对应的值T result = (T)e.value;return result;}}// map还没有初始化时创建map对象,并设置null,同时返回nullreturn setInitialValue();
}
ThreadLocal#remove
方法的源码:
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());// 键在直接移除if (m != null) {m.remove(this);}
}
4、ThreadLocal 内存泄漏问题
4.1、ThreadLocal 内存泄漏问题发生的原因
ThreadLocal 自身并不储存值,而是作为 一个 key 来让线程从 ThreadLocal 获取 value。而 Entry 是中的 key 是弱引用(Entry extends WeakReference<ThreadLocal<?>>
),如果一个 ThreadLocal 没有外部强引用来引用它,那么系统 GC 的时候,这个 ThreadLocal 势必会被回收。
并且,作为 ThreadLocalMap 的 key,ThreadLocal 被回收后,ThreadLocalMap 就会存在 key 为 null,但 value 不为 null 的 Entry(其实,在预防 ThreadLocal 内存泄漏问题上,Java 也做了一些努力:Java 在 Thread 中维护了 ThreadLocalMap,所以 ThreadLocalMap 的生命周期和 Thread(当前线程)一样长。并且,在 ThreadLocal 中,进行 get,set 操作的时候会清除 Map 里所有 key 为 null 的 value。)
但是,若当前线程一直不结束,可能是作为线程池中的一员,线程结束后不被销毁,或者分配(当前线程又创建了 ThreadLocal 对象)使用了又不再调用 get/set 方法,就可能引发内存泄漏。
其次,就算线程结束了,操作系统在回收线程或进程的时候不是一定杀死线程或进程的,在繁忙的时候,只会清除线程或进程数据的操作,重复使用线程或进程(线程 id 可能不变导致内存泄漏)。因此,key 弱引用并不是导致内存泄漏的原因,而是因为 ThreadLocalMap 的生命周期与当前线程一样长,并且没有手动删除对应 value。
4.2、为什么使用弱引用
通过对上述问题的分析我们可以发现,ThreadLocal 内存泄漏的一个主要原因就是 Entry 是中的 key 是弱引用,那这就有一个问题值得思考:为什么使用弱引用而不是强引用?
较为官方的说法是:为了应对非常大和长时间的用途,哈希表使用弱引用的 key。
下面我们分两种情况讨论:
- key 使用强引用:引用的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删除,ThreadLocal 不会被回收,导致 Entry 内存泄漏。
- key 使用弱引用**:**引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set,get,remove 的时候会被清除。
比较两种情况,我们可以发现:
由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set,get,remove 的时候会被清除。
因此,ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏,而不是因为弱引用。
4.3、ThreadLocal 最佳实践
综合上面的分析,我们可以理解 ThreadLocal 内存泄漏的前因后果,那么怎么避免内存泄漏呢?
每次使用完 ThreadLocal,都调用它的 remove() 方法,清除数据。
在使用线程池的情况下,没有及时清理 ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用 ThreadLocal 就跟加锁完要解锁一样,用完就清理。
5、Thread 相关知识点
5.1、关于 ThreadLocal 和 Synchronized 的区别
相同点:ThreadLocal 和 Synchronized 都是为了解决多线程中访问相同变量的冲突问题。
不同点:
- ThreadLocal:以空间换时间,为每个线程提供一个变量副本,消耗较多的内存,但是多个线程可以同时访问该变量而且相互不会影响。
- Synchronized:以时间换空间,多个线程访问的是同一个变量,但是当多个线程同时访问该变量时,需要抢占锁,并且等待获取锁的线程释放锁,会消耗较多的时间。
5.2、关于 ThreadLocalMap 中的 Hash 冲突处理
ThreadLocalMap 作为一个 HashMap 和 java.util.HashMap 的实现是不同的。对于 java.util.HashMap 使用的是链表法来处理冲突。
但是,对于 ThreadLocalMap,它使用的是简单的线性探测法,如果发生了元素冲突,那么就使用下一个槽位存放。具体来说:
- 当插入一个键值对时,如果当前索引位置已经被占用,则继续探测下一个位置,直到找到一个空闲的位置为止。
- 这种方法的优点是实现简单,缺点是当发生冲突时可能会导致探测时间较长,特别是在负载因子较高的情况下。
通过对比可以看出,ThreadLocalMap 的线性探测法更适合于线程本地变量的存储,因为在多数情况下,ThreadLocalMap 的负载因子较低,冲突较少,线性探测法的性能影响较小。
相关文章:

Java 并发编程:线程变量 ThreadLocal
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 029 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进…...

【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】018 - init_sequence_f 各函数源码分析(二)
【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】018 - init_sequence_f 各函数源码分析(二) 一、arch_cpu_init二、arch_cpu_init系列文章汇总:《【OpenHarmony4.1 之 U-Boot 源码深度解析】000 - 文章链接汇总》 本文链接:《【OpenHarmony4.1 之 U-Boot 2024.07源码深度…...

LVS原理——详细介绍
目录 介绍 lvs简介 LVS作用 LVS 的优势与不足 LVS概念与相关术语 LVS的3种工作模式 LVS调度算法 LVS-dr模式 LVS-tun模式 ipvsadm工具使用 实验 nat模式集群部署 实验环境 webserver1配置 webserver2配置 lvs配置 dr模式集群部署 实验环境 router 效果呈现…...

MYSQL 5.7.36 等保 建设记录
文章目录 前言一、开启审计日志1.1 查看当前状态1.2 开启方式1.3 查看开启后状态 二、密码有效期2.1 查看当前状态2.2 开启方式2.3 查看开启后状态 三、密码复杂度3.1 查看当前状态3.2 开启方式3.3 查看开启后状态 四、连接控制4.1 查看当前状态4.2 开启方式4.3 查看开启后状态…...

fatal: unable to access ‘https://github.com/xxxxx
ubuntu中git克隆项目异常 git clone https://github.com/xxx Cloning into ‘xxx’… fatal: unable to access ‘https://github.com/xxx/xx.git/’: Could not resolve host: github.com 解决办法使用命令: git config --global http.proxy git config --global…...

从零开始的CPP(38)——递归与动态规划
leetcode46 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1: 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2: 输入&#…...

从战略到系统架构:信息系统设计的全面解析
在当今数字化时代,信息系统已成为企业运营、管理和创新的核心驱动力。信息系统设计的重要性不仅关乎企业的技术实现,更直接影响到企业的战略执行和市场竞争能力。本文将从战略视角出发,深入探讨信息系统设计的全过程,包括从战略制…...

GEE调用中国(China Land Cover Dataset,简称CLCD)1990-2022年30米分辨率的土地分类数据
博客推荐 GEE土地分类:中国30米年度土地覆盖产品annual China Land Cover Dataset, CLCD(面积提取)_30米土地利用数据gee-CSDN博客 简介 中国陆地覆盖数据集(China Land Cover Dataset,简称CLCD)是一个用…...

三十八、大数据技术之Kafka(1)
🌻🌻 目录 一、Kafka 概述1.1 定义1.2 消息队列1.2.1 消息队列内部实现原理1.2.2 传统消息队列的应用场景1.2.3 消息队列的两种模式 1.3 Kafka 基础架构 二、 Kafka 快速入门2.1 安装前的准备2.2 安装部署2.2.1 集群规划2.2.2 单节点或集群部署2.2.3 集群…...

将 Tcpdump 输出内容重定向到 Wireshark
在 Linux 系统中使用 Tcpdump 抓包后分析数据包不是很方便。 通常 Wireshark 比 tcpdump 更容易分析应用层协议。 一般的做法是在远程主机上先使用 tcpdump 抓取数据并写入文件,然后再将文件拷贝到本地工作站上用 Wireshark 分析。 还有一种更高效的方法…...

【Python蓝屏程序(管理员)】
说明:该程序为临摹(😀)作品,源地址C蓝屏程序(非管理员) 我试图使用Python调用 NtRaiseHardError API ,实现类似的蓝屏效果。可惜我发现Python在普通权限下,直接调用 NtRaiseHardError API 是不被允许的,因为…...

OpenGL ES->GLSurfaceView绘制图形的流程
自定义View代码 class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs), GLSurfaceView.Renderer {var mProgrem 0init {// 设置 OpenGL ES 3.0 版本setEGLContextClientVersion(3)// 设置当前类为渲染器, 注册回调接口的实现类set…...

Linux OOM Killer详解
Linux OOM Killer详解 一、概述二、OOM Killer的技术原理1. 内存区域划分2. 内存耗尽与OOM Killer触发3. 选择被杀进程的策略4. 内存回收机制5. 内存分配策略 三、OOM Killer的工作机制1. 内存压力监测2. 触发条件3. 选择被杀进程4. 终止进程 四、实际场景举例场景一࿱…...

2024rk(案例二)
试题二(25分) 阅读以下关于数据库缓存的叙述,在答题纸上回答问题1至问题3。 【说明】 某大型电商平台建立了一个在线 B2B 商店系统,并在全国多地建设了货物仓储中心,通过提前备货的方式来提高货物的运送效率。但是在运营过程中,发现会出现很多跨仓储中心调货从而延误货物…...

小红书爆文秘籍:ChatGPT助你从0到1创造热门内容!
在小红书打造爆款文案的策略中,以下是一些调整和同义词替换的建议,以便达到文章去重的要求: 了解目标受众: 在撰写文案前,先深入分析目标读者的属性,如年龄层次、性别、爱好和购买行为。通过ChatGPT, 你能迅…...

django快速实现个人博客(附源码)
文章目录 一、工程目录组织结构二、模型及管理实现1、模型2、admin管理 三、博客展现实现1、视图实现2、模板实现 四、部署及效果五、源代码 Django作为一款成熟的Python Web开发框架提供了丰富的内置功能,如ORM(对象关系映射)、Admin管理界面…...

K8s部署篇之手动部署二进制高可用集群架构
一、系统环境初始化 一)架构设计 所有节点都操作:3个master(etcd集群三个节点)和2个node 1、K8s服务调用如图 2、各组件说明 1、API Server 供Kubernetes API接口,主要处理 REST操作以及更新ETCD中的对象所有资源增删…...

【Unity/XLua】xlua自带教程示例分析(6)—— lua协程
文章目录 工具准备协程测试 工具准备 首先是工具脚本,一个Coroutine_Runner.cs和一个cs_coroutine.lua 前者定义了一个继承自Monobehavior的脚本组件,后者则使用lua去在Unity中实例化一个挂载该组件的GameObject,并将其设置为DontDestroyOn…...

CV目标检测概述
文章目录 目标检测概述目标检测图像分割目标检测和图像分割的区别 目标检测概述 目标检测和图像分割是计算机视觉中的两个重要任务,它们有着不同的目的和应用。以下是它们的简要介绍和区别: 目标检测 目标检测(Object Detection࿰…...

如何在notebook中运行nodejs
在 Python 生态系统的推动下,机器学习和人工智能日益流行,这带来了计算笔记本的概念。这些交互式计算平台主要是为以 Python 为中心的数据科学应用而开发的,它们将代码、计算输出、解释性文本和多媒体合并成一个有内聚力的文档。 作为 JavaS…...

Mybatis学习-day19
Mybatis学习-day19 1. resultMap resultMap 是 MyBatis 中最复杂的元素,主要用于解决实体类属性名与数据库表中字段名不一致的情况,可以将查询结果映射成实体对象。 <resultMap id"staffAndDep" type"com.easy.bean.Staff">…...

IDEA构建SpringBoot多模块项目
前言 最近一直在思考一个问题,springboot的多模块项目到底是怎么运行和运作的? 一般我们大部分的springboot项目都是单模块的项目,但是如果后续有要求开发多模块的项目应该怎么处理?于是基于这点进行了研究。 本次文章将会带大…...

【前端】NodeJS:nvm
文章目录 1 介绍2 使用2.1 下载安装2.2 常用命令 1 介绍 nvm全称:Node Version Manager,顾名思义它是用来管理node版本的工具,方便切换不同版本的Node.js。 2 使用 nvm的使用非常的简单,跟npm的使用方法类似。 2.1 下载安装 …...

Docker网络模式及通信
一、Docker默认的网络通信 1.1 Docker安装后默认的网络设置 Docker服务器安装完成之后,默认在每个宿主机会生成一个名称为docker0的网卡,其IP地址都是172.17.0.1/16 [rootubuntu1804 ~]#apt -y install bridge-utils [rootubuntu1804 ~]#brctl show 另…...

类模板实现实现Qt click/hover自定义操作
一、场景 常常会需要实现点击/hover时修改图片,可能是一个QPushButton、QLabel、QToolButton…… 由于Qt bug,QIcon/QSS只能实现常规态、按下态的图标切换,hover态的图片设置无效。 解决思路无非是安装事件过滤器、自定义类并重实现事件。 …...

Arco Design:引领未来的Vue 3创意先锋,一键开启高效与美感并重的Web开发之旅!
Arco Design 是一个基于 Vue 3 的 UI 框架,它提供了丰富的组件和样式,可以帮助开发者快速构建高质量的 Web 应用程序。以下是 Arco Design 的一些详细特点: 完整的设计系统:Arco Design 提供了一套完整的设计系统,包括…...

【MySQL】Linux下用C/C++链接MySQL数据库
文章目录 一、准备工作二、验证库和接口的使用三、链接数据库四、对数据库进行增删查改增删改查 五、结尾 一、准备工作 要使用C链接数据库, 首先要去MySQL官网下载官网提供的库, MySQL 社区下载. 如图所示: 接着选择: 按需选择版本: 如果用的是云服务器, 那么在安装mysql时…...

Python金融量化专栏简介
量化分析实战 - 专栏大纲 👉👉👉 《玩转Python金融量化专栏》👈👈👈 订阅本专栏的可以下载对应的代码和数据集 专栏目标 本专栏旨在帮助读者全面掌握使用Python进行金融技术指标的计算与应用,从基础到高级,涵盖各种技术指标的实现、策略开发与回测等内容。通过…...

出行365:依托分布式数据库,让出行无忧 | OceanBase案例
*本文首发自“新华社环球”杂志,作者张海鑫 每年的暑期旅游旺季,都会触发一轮轮的文旅消费的热潮,对于互联网出行服务行业而言,这既是一场盛大的狂欢,也是对其综合实力的严峻考验。 然而,自去年暑假起&…...

【C语言】位段详解
🦄个人主页:小米里的大麦-CSDN博客 🎏所属专栏:https://blog.csdn.net/huangcancan666/category_12718530.html 🎁代码托管:黄灿灿 (huang-cancan-xbc) - Gitee.com ⚙️操作环境:Visual Studio 2022 目录 一、什么是位段? 二、…...