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

多线程的基本原理学习

由一个问题引发的思考

线程的合理使用能够提升程序的处理性能,主要有两个方面,第一个是能够利用多核cpu以及超线程技术来实现线程的并行执行;第二个是线程的异步化执行相比于同步执行来说,异步执行能够很好的优化程序的处理性能提升并发吞吐量 ,同时,也带来了很多麻烦,举个简单的例子 多线程对于共享变量访问带来的安全性问题 一个变量i. 假如一个线程去访问这个变量进行修改,这个时候对于数据的修改和访问没有任何问题。但是如果多个线程对于这同一个变量进行修改,就会存在一个数据安全性问题 。

在这里插入图片描述

对于线程安全性,本质上是管理对于数据状态的访问,而且这个这个状态通常是共享的、可变的。共享,是指这个数据变量可以被多个线程访问;可变,指这个变量的值在它的生命周期内是可以改变的。
一个对象是否是线程安全的,取决于它是否会被多个线程访问,以及程序中是如何去使用这个对象的。所以,如果多个线程访问同一个共享对象,在不需额外的同步以及调用端代码不用做其他协调的情况下,这个共享对象的状态依然是正确的(正确性意味着这个对象的结果与我们预期规定的结果保持一致),那说明这个对象是线程安全的。
package com.sp.demo;/*** @author : lssffy* @Description :* @date : 2024/2/16 18:02*/
public class Demo {private static int count = 0;public static void inc(){try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}count++;}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 1000;i++){new Thread(()->Demo.inc()).start();}Thread.sleep(3000);System.out.println("运行结果:" + count);}
}

思考如何保证线程并行的数据安全性

问题的本质在于共享数据存在并发访问。如果我们能够有一种方法使得线程的并行变成串行,那是不是就不存在这个问题呢? 按照大家已有的知识,最先想到的应该就是锁吧。  毕竟这个场景并不模式,我们在和数据库打交道的时候,就了解过悲观锁、乐观锁的概念。 什么是锁?它是处理并发的一种同步手段,而如果需要达到前面我们说的一个目的,那么这个锁一定需要实现互斥的特性。 Java 提供的加锁方法就是Synchroinzed关键字。

synchronized 的基本认识

在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着 Java SE 1.6 对synchronized 进行了各种优化之后,有些情况下它就并不那么重,Java SE 1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁。这块在后续我们会慢慢展开.

synchronized 的基本语法

synchronized 有三种方式来加锁,分别是 
1. 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁 获得当前类对象的锁 
2. 静态方法,作用于当前类对象加锁,进入同步代码前要
3. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 不同的修饰类型,代表锁的控制粒度 
synchronized 的应用 
修改前面的案例,使用synchronized关键字后,可以达到数据安全的效果 
package com.sp.demo;/*** @author : lssffy* @Description :* @date : 2024/2/16 18:02*/
public class Demo {private static int count = 0;public static void inc(){**synchronized** (Demo.class) {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}count++;}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 1000;i++){new Thread(()->Demo.inc()).start();}Thread.sleep(3000);System.out.println("运行结果:" + count);}
}

思考锁是如何存储的

可以思考一下,要实现多线程的互斥特性,那这把锁需要哪些因素? 
1. 锁需要有一个东西来表示,比如获得锁是什么状态、无锁状态是什么状态 
2. 这个状态需要对多个线程共享 那么我们来分析,synchronized锁是如何存储的呢?观察synchronized 的整个语法发现,synchronized(lock)是基于存储和这个lock对象有关系呢? 
对象在内存中的布局 
lock 这个对象的生命周期来控制锁粒度的,那是不是锁的于是我们以对象在jvm内存中是如何存储作为切入点,去看看对象里面有什么特性能够实现锁 在Hotspot 虚拟机中,对象在内存中的存储布局,可以分为三个区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding) 

在这里插入图片描述

探究Jvm源码实现 
当我们在Java 代码中,使用new 创建一个对象实例的时候,(hotspot 虚拟机)JVM 层面实际上会创建一个 instanceOopDesc 对象。 
Hotspot 虚拟机采用 OOP-Klass 模型来描述 Java 对象实例,OOP(Ordinary Object Point)指的是普通对象指针,Klass 用来描述对象实例的具体类型。Hotspot 采用instanceOopDesc 和 arrayOopDesc 来描述对象头,arrayOopDesc 对象用来描述数组类型 instanceOopDesc 的定义在 Hotspot 源 码 中 的 instanceOop.hpp 文件中,另外,arrayOopDesc 的定义对应 arrayOop.hpp.

在这里插入图片描述
从 instanceOopDesc 代码中可以看到 instanceOopDesc继承自oopDesc,oopDesc 的定义载 Hotspot 源码中的 oop.hpp 文件中 是 _mark和 _metadata
在普通实例对象中,oopDesc的定义包含两个成员,分别_mark 表示对象标记、属于markOop类型,也就是接下来要讲解的Mark World,它记录了对象和锁有关的信息 _metadata 表示类元信息,类元信息存储的是对象指向它的类元数据(Klass)的首地址,其中 Klass 表示普通指针、 _compressed_klass 表示压缩类指针

MarkWord

在Hotspot 中,markOop 的定义在 markOop.hpp 文件中,代码如下

在这里插入图片描述
Mark word 记录了对象和锁有关的信息,当某个对象被synchronized 关键字当成同步锁时,那么围绕这个锁的一系列操作都和Mark word有关系。Mark Word在32位虚拟机的长度是32bit、在64位虚拟机的长度是64bit。 Mark Word里面存储的数据会随着锁标志位的变化而变化,Mark Word 可能变化为存储以下5中情况.
在这里插入图片描述
为什么任何对象都可以实现锁
1. 首先,Java 中的每个对象都派生自 Object 类,而每个Java Object 在 JVM 内部都有一个 native 的 C++对象 oop/oopDesc 进行对应。
2. 线程在获取锁的时候,实际上就是获得一个监视器对象(monitor) ,monitor 可以认为是一个同步对象,所有的Java 对象是天生携带 monitor。在 hotspot 源码的 markOop.hpp 文件中,可以看到下面这段代码。
在这里插入图片描述
多个线程访问同步代码块时,相当于去争抢对象监视器修改对象中的锁标识,上面的代码中ObjectMonitor这个对象和线程争抢锁的逻辑有密切的关系 .

synchronized 锁的升级

在分析markword 时,提到了偏向锁、轻量级锁、重量级锁。在分析这几种锁的区别时,我们先来思考一个问题 使用锁能够实现数据的安全性,但是会带来性能的下降。不使用锁能够基于线程并行提升程序性能,但是却不能保证线程安全性。这两者之间似乎是没有办法达到既能满足性能也能满足安全性的要求。 hotspot 虚拟机的作者经过调查发现,大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且总是由同一个线程多次获得。所以基于这样一个概率,是的synchronized在JDK1.6 之后做了一些优化,为了减少获得锁和释放锁带来的性能开销,引入了偏向锁、轻量级锁的概念。因此大家会发现在synchronized中,锁存在四种状态 分别是:无锁、偏向锁、轻量级锁、重量级锁; 锁的状态根据竞争激烈的程度从低到高不断升级。 

偏向锁的基本原理

前面说过,大部分情况下,锁不仅仅不存在多线程竞争,而是总是由同一个线程多次获得,为了让线程获取锁的代价更低就引入了偏向锁的概念。怎么理解偏向锁呢? 当一个线程访问加了同步锁的代码块时,会在对象头中存储当前线程的ID,后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁。如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了。

偏向锁的获取和撤销逻辑

1. 首先获取锁 对象的Markword,判断是否处于可偏向状态。( biased_lock=1、且 ThreadId 为空)。
2. 如果是可偏向状态,则通过CAS操作,把当前线程的ID写入到MarkWord 
a) 如果 cas 成功,那么markword 就会变成这样。表示已经获得了锁对象的偏向锁,接着执行同步代码块 
b) 如果 cas 失败,说明有其他线程已经获得了偏向锁,这种情况说明当前锁存在竞争,需要撤销已获得偏向锁的线程,并且把它持有的锁升级为轻量级锁(这个操作需要等到全局安全点,也就是没有线程在执行字节码)才能执行 
3. 如果是已偏向状态,需要检查 markword 中存储的ThreadID 是否等于当前线程的ThreadID 块 

偏向锁的撤销

a) 如果相等,不需要再次获得锁,可直接执行同步代码
b) 如果不相等,说明当前锁偏向于其他线程,需要撤销偏向锁并升级到轻量级锁 偏向锁的撤销并不是把对象恢复到无锁可偏向状态(因为偏向锁并不存在锁释放的概念),而是在获取偏向锁的过程中,发现cas 失败也就是存在线程竞争时,直接把被偏向的锁对象升级到被加了轻量级锁的状态。
对原持有偏向锁的线程进行撤销时,原获得偏向锁的线程有两种情况: 
1. 原获得偏向锁的线程如果已经退出了临界区,也就是同步代码块执行完了,那么这个时候会把对象头设置成无锁状态并且争抢锁的线程可以基于 CAS 重新偏向但前线程 
2. 如果原获得偏向锁的线程的同步代码块还没执行完,处于临界区之内,这个时候会把原获得偏向锁的线程升级为轻量级锁后继续执行同步代码块 在我们的应用开发中,绝大部分情况下一定会存在2个以的资源消耗。所以可以通过jvm参数 上的线程竞争,那么如果开启偏向锁,反而会提升获取锁UseBiasedLocking 来设置开启或关闭偏向锁。
**流程图分析**

在这里插入图片描述

轻量级锁的基本原理

轻量级锁的加锁和解锁逻辑
锁升级为轻量级锁之后,对象的Markword也会进行相应的的变化。升级为轻量级锁的过程: 
1. 线程在自己的栈桢中创建锁记录 LockRecord。 
2. 将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。 
3. 将锁记录中的Owner指针指向锁对象。 
4. 将锁对象的对象头的MarkWord替换为指向锁记录的指针。 

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

自旋锁

轻量级锁在加锁过程中,用到了自旋锁 所谓自旋,就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。 注意,锁在原地循环的时候,是会消耗cpu的,就相当于在执行一个啥也没有的for循环。 
所以,轻量级锁适用于那些同步代码块执行的很快的场景,这样,线程原地等待很短的时间就能够获得锁了。 自旋锁的使用,其实也是有一定的概率背景,在大部分同步代码块执行的时间都是很短的。所以通过看似无异议的循环反而能提升锁的性能。 但是自旋必须要有一定的条件控制,否则如果一个线程执行同步代码块的时间很长,那么这个线程不断的循环反而会消耗CPU资源。默认情况下自旋的次数是10次, 可以通过 preBlockSpin来修改在JDK1.6 之后,引入了自适应自旋锁,自适应意味着自旋的次数不是固定不变的,而是根据前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定。 如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源 

轻量级锁的解锁

轻量级锁的锁释放逻辑其实就是获得锁的逆向逻辑,通过CAS 操作把线程栈帧中的LockRecord 替换回到锁对象的MarkWord 中,如果成功表示没有竞争。如果失败,表示当前锁存在竞争,那么轻量级锁就会膨胀成为重量级锁 
**流程图分析** 

在这里插入图片描述

重量级锁的基本原理
当轻量级锁膨胀到重量级锁之后,意味着线程只能被挂起阻塞来等待被唤醒了。
**重量级锁的monitor**
加了同步代码块以后,在字节码中会看到一个monitorenter 和 monitorexit。 
每一个JAVA 对象都会与一个监视器monitor 关联,我们可以把它理解成为一把锁,当一个线程想要执行一段被synchronized 修饰的同步方法或者代码块时,该线程得先获取到synchronized修饰的对象对应的monitor。 monitorenter 表示去获得一个对象监视器。monitorexit表示释放monitor 监视器的所有权,使得其他被阻塞的线程可以尝试去获得这个监视器 
monitor 依赖操作系统的MutexLock(互斥锁)来实现的, 线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能 。
重量级锁的加锁的基本流程

在这里插入图片描述

任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。 
回顾线程的竞争机制
再来回顾一下线程的竞争机制对于锁升级这块的一些基本流程。方便大家更好的理解 加入有这样一个同步代码块,存在Thread#1、Thread#2等多个线程 
synchronized (lock) { // do something 
}
情况一:只有Thread#1会进入临界区; 
情况二:Thread#1和Thread#2交替进入临界区,竞争不激烈; 
情况三:Thread#1/Thread#2/Thread3… 同时进入临界区,竞争激烈

偏向锁
此时当Thread#1进入临界区时,JVM会将lockObject的对象头Mark Word的锁标志位设为“01”,同时会用CAS操作把Thread#1 的线程 ID记录到Mark Word 中,此时进入偏向模式。所谓“偏向”,指的是这个锁会偏向于Thread#1,若接下来没有其他线程进入临界区,则 Thread#1 再出入临界区无需再执行任何同步操作。也就是说,若只有Thread#1 会进入临界区,实际上只有Thread#1初次进入临界区时需要执行CAS操作,以后再出入临界区都不会有同步操作带来的开销。
轻量级锁
偏向锁的场景太过于理想化,更多的时候是 Thread#2 也会尝试进入临界区, 如果 Thread#2 也进入临界区但是级锁的方式来获取锁
重量级锁
Thread#1 还没有执行完同步代码块时,会暂停 Thread#1并且升级到轻量级锁。Thread#2通过自旋再次尝试以轻量如果Thread#1和Thread#2正常交替执行,那么轻量级锁基本能够满足锁的需求。但是如果Thread#1和Thread#2同时进入临界区,那么轻量级锁就会膨胀为重量级锁,意味着Thread#1 线程获得了重量级锁的情况下,Thread#2就会被阻塞。

Synchronized 结合 Java Object 对象中的 wait,notify,notifyAll

前面我们在讲synchronized的时候,发现被阻塞的线程什么时候被唤醒,取决于获得锁的线程什么时候执行完同步代码块并且释放锁。那怎么做到显示控制呢?我们就需要借助一个信号机制: 在 Object 对象中,提供了wait/notify/notifyall,可以用于控制线程的状态 

wait/notify/notifyall 基本概念

wait:表示持有对象锁的线程A准备释放对象锁权限,释放cpu资源并进入等待状态。 
notify:表示持有对象锁的线程A准备释放对象锁权限,通知 jvm 唤醒某个竞争该对象锁的线程 X。线程 A 
synchronized 代码执行结束并且释放了锁之后,线程X直接获得对象锁权限,其他竞争线程继续等待(即使线程X同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的notify ,notifyAll 被调用)。 
notifyAll:notifyall 和 notify 的区别在于,notifyAll 会唤醒所有竞争同一个对象锁的所有线程,当已经获得锁的线程A 释放锁之后,所有被唤醒的线程都有可能获得对象锁权限。
需要注意的是:三个方法都必须在synchronized 同步关键字 所 限定的作用域中调用,否则会报错java.lang.IllegalMonitorStateException ,意思是因为没有同步,所以线程对对象锁的状态是不确定的,不能调用这些方法。 另外,通过同步机制来确保线程从wait方法返回时能够感知到感知到notify线程对变量做出的修改。

wait/notify 的基本原理

在这里插入图片描述

相关文章:

多线程的基本原理学习

由一个问题引发的思考 线程的合理使用能够提升程序的处理性能&#xff0c;主要有两个方面&#xff0c;第一个是能够利用多核cpu以及超线程技术来实现线程的并行执行&#xff1b;第二个是线程的异步化执行相比于同步执行来说&#xff0c;异步执行能够很好的优化程序的处理性能提…...

C/C++进制转换

十进制转化为二进制 进制转化#include <iostream> using namespace std;void change(int); int main() {int num;cout << "请输入一个十进制数: ";cin >> num;cout << "转化后的二进制数为: ";change(num);return 0; } void chan…...

使用 Coze 搭建 TiDB 助手

导读 本文介绍了使用 Coze 平台搭建 TiDB 文档助手的过程。通过比较不同 AI Bot 平台&#xff0c;突出了 Coze 在插件能力和易用性方面的优势。文章深入讨论了实现原理&#xff0c;包括知识库、function call、embedding 模型等关键概念&#xff0c;最后成功演示了如何在 Coze…...

Arduino程序简单入门

文章目录 一、结构1.1 setup()1.2 loop() 二、结构控制2.1 if2.2 if...else2.3 switch case2.4 for2.5 while2.6 do...while2.7 break2.8 continue2.9 return2.10 goto 三、扩展语法3.1 ;&#xff08;分号&#xff09;3.2 {}&#xff08;花括号&#xff09;3.3 //&#xff08;单…...

QT+OSG/osgEarth编译之八十三:osgdb_ogr+Qt编译(一套代码、一套框架,跨平台编译,版本:OSG-3.6.5插件库osgdb_ogr)

文章目录 一、osgdb_ogr介绍二、文件分析三、pro文件四、编译实践一、osgdb_ogr介绍 osgDB是OpenSceneGraph(OSG)库中的一个模块,用于加载和保存3D场景数据。osgDB_ogr是osgDB模块中的一个插件,它提供了对OGR(开放地理空间联盟)库的支持。 OGR是一个开源的地理空间数据…...

开年炸裂-Sora/Gemini

最新人工智能消息 谷歌的新 Gemini 模型 支持多达 1M的Token&#xff0c;可以分析长达一小时的视频 1M Token可能意味着分析700,000 个单词、 30,000 行代码或11 小时的音频、总结、改写和引用内容。 Comment&#xff1a;google公司有夸大的传统&#xff0c;所以真实效果需要上…...

vue前端系统启动报错Module not found: Error: Can‘t resolve ‘sass-loader‘

1、确认项目中是否已安装 node-sass 包。sass-loader 是依赖于 node-sass 包的&#xff0c;如果没有安装 node-sass 包&#xff0c;也会导致无法找到 sass-loader 包。 npm ls node-sass安装 node-sass 包&#xff1a; npm install --save-dev node-sass2、确认项目中是否已安…...

HTML | DOM | 网页前端 | 常见HTML标签总结

文章目录 1.前端开发简单分类2.前端开发环境配置3.HTML的简单介绍4.常用的HTML标签介绍 1.前端开发简单分类 前端开发&#xff0c;这里是一个广义的概念&#xff0c;不单指网页开发&#xff0c;它的常见分类 网页开发&#xff1a;前端开发的主要领域&#xff0c;使用HTML、CS…...

乡政府|乡政府管理系统|基于Springboot的乡政府管理系统设计与实现(源码+数据库+文档)

乡政府管理系统目录 目录 基于Springboot的乡政府管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、活动信息管理 3、新闻类型管理 4、新闻动态管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推…...

存储系统如何规避数据静默错误SDC?

存储系统规避数据静默错误&#xff08;Silent Data Corruption, SDC&#xff09;是一项复杂且关键的任务&#xff0c;涉及多个层次的技术和策略。数据静默错误是指在存储或传输过程中发生的错误&#xff0c;这些错误未被检测出来&#xff0c;因此无法立即纠正&#xff0c;可能导…...

《Linux 简易速速上手小册》第8章: 安全性与加固(2024 最新版)

文章目录 8.1 防火墙与安全策略8.1.1 重点基础知识8.1.2 重点案例&#xff1a;配置 iptables 以保护 Web 服务器8.1.3 拓展案例 1&#xff1a;使用 firewalld 配置动态防御区域8.1.4 拓展案例 2&#xff1a;配置 ufw 以简化管理 8.2 SSH 安全最佳实践8.2.1 重点基础知识8.2.2 重…...

Ubuntu Desktop 显示文件路径

Ubuntu Desktop 显示文件路径 1. GUI hot key2. CLIReferences 1. GUI hot key Ctrl L: 显示文件路径 2. CLI right click -> Open in Terminal -> pwd strongforeverstrong:~/Desktop$ pwd /home/strong/DesktopReferences [1] Yongqiang Cheng, https://yongqiang…...

【Java程序设计】【C00270】基于Springboot的moba类游戏攻略分享平台(有论文)

基于Springboot的moba类游戏攻略分享平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的游戏攻略分享平台 本系统分为系统功能模块、管理员功能模块、以及用户后台功能模块。 系统功能模块&#xff1a;在平台首…...

【旧文更新】【优秀毕设】人脸识别打卡/签到/考勤管理系统(OpenCV+最简基本库开发、可移植树莓派 扩展网络图像推流控制 验证码及Excel邮件发送等功能)

【旧文更新】【优秀毕设】人脸识别打卡/签到/考勤管理系统&#xff08;OpenCV最简基本库开发、可移植树莓派 扩展网络图像推流控制 验证码及Excel邮件发送等功能&#xff09; 文章目录 关于旧文新发毕设结构主页面验证码识别效果管理页面人脸信息采集管理实时数据更新签到结果…...

模型 4i(趣味、利益、互动、个性)理论

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。重在提升认知。以用户为中心营销。 1 模型 4i(趣味、利益、互动、个性)理论的应用 1.1 4i理论在电子商务中的应用 小米公司在其电子商务平台上运用了 4i理论&#xff0c;取得了较好的效果。具体表现如下…...

解线性方程组(二)——Jacobi迭代法求解(C++)

迭代法 相比于直接法求解&#xff0c;迭代法使用多次迭代来逐渐逼近解&#xff0c;其精度比不上直接法&#xff0c;但是其速度会比直接法快很多&#xff0c;计算精度可控&#xff0c;特别适用于求解系数矩阵为大型稀疏矩阵的方程组。 Jacobi迭代法 假设有方程组如下&#xf…...

信息安全技术基础知识

一、考点分布 信息安全基础&#xff08;※※&#xff09;信息加密解密技术&#xff08;※※※&#xff09;密钥管理技术&#xff08;※※&#xff09;访问控制及数字签名技术&#xff08;※※※&#xff09;信息安全的保障体系 二、信息安全基础 信息安全包括5个基本要素&#…...

使用Taro开发鸿蒙原生应用——快速上手,鸿蒙应用开发指南

导读 本指南为开发者提供了使用 Taro 框架开发鸿蒙原生应用的快速入门方法。Taro&#xff0c;作为一个多端统一开发框架&#xff0c;让开发者能够使用一套代码同时适配多个平台&#xff0c;包括鸿蒙系统。文章将详细介绍如何配置开发环境&#xff0c;以及如何利用 Taro 的特性…...

C语言指针(初阶)

文章目录 1:内存与地址1.1内存1.2:如何理解编址 2:指针变量与地址2.1:指针变量与解引用操作符2.1.1:指针变量2.1.2:如何拆解指针类型2.1.3:解引用操作符 2.2:指针变量的大小 3:指针变量类型的意义代码1解引用修改前解引用修改后 代码2解引用修改前解引用修改后 4:const修饰指针…...

Python循环语句——for循环的嵌套使用

一、引言 在Python编程中&#xff0c;循环是控制程序流程的重要工具&#xff0c;它允许我们重复执行某段代码&#xff0c;直到满足特定的条件为止。其中&#xff0c;for循环是Python中最常用的循环类型之一。而嵌套循环&#xff0c;即在一个循环内部再嵌套另一个循环&#xff…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

认识CMake并使用CMake构建自己的第一个项目

1.CMake的作用和优势 跨平台支持&#xff1a;CMake支持多种操作系统和编译器&#xff0c;使用同一份构建配置可以在不同的环境中使用 简化配置&#xff1a;通过CMakeLists.txt文件&#xff0c;用户可以定义项目结构、依赖项、编译选项等&#xff0c;无需手动编写复杂的构建脚本…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...