最新要闻
- 世界速讯:鹏都农牧:缅甸肉牛暂无法通关,公司正在积极推进复工复产工作
- 天天看点:10岁小学生玩游戏18天花掉14万 退款申请腾讯无视:家长感慨一定看好支付密码
- 焦点精选!摩托罗拉Razr 2023外观曝光:超大外屏抢眼
- 全球微头条丨安卓都16GB内存了 iPhone 15 Pro还在挤牙膏
- 【时快讯】或6.58万起 疑似比亚迪海鸥售价曝光:最远能跑405km
- 中国香港影史首部票房破亿华语电影诞生 此前TOP10均为好莱坞
- 世界热头条丨每天走路10000步很健康?错 大错特错!
- 踢翻日系!比亚迪又要对BBA动手了
- 当前报道:20年来最低点!PC出货量暴跌:Intel等巨头业绩“凉凉” 显卡存储也没人买
- 世界微动态丨理想汽车:被4S店PUA、不如看看直营无套路零售中心
- 网红玩具有危险!2岁幼童误吞28颗磁力珠 医生提醒:必须及时取出
- 叫板特斯拉 保时捷:单踏板模式制动能量回收效率不高
- 座位被占 女子爬火车行李架睡觉:千万不要模仿
- 环球资讯:WiFi万能钥匙联合长安汽车,“一键连接”拓展至车机端
- 灵魂摆渡的作者真的杀了他的妻子吗?灵魂摆渡的经典台词有哪些?
- 董明珠回应直播带货争议:不是为了卖产品 是推广技术
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
通讯!98%的程序员,都没有研究过JVM重排序和顺序一致性
文章整理自 博学谷狂野架构师
(资料图)
重排序
数据依赖性
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:
名称 | 代码示例 | 说明 |
---|---|---|
写后读 | a = 1;b = a; | 写一个变量之后,再读这个位置。 |
写后写 | a = 1;a = 2; | 写一个变量之后,再写这个变量。 |
读后写 | a = b;b = 1; | 读一个变量之后,再写这个变量。 |
上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。
前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。
as-if-serial语义
as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。为了具体说明,请看下面计算圆面积的代码示例:
COPYdouble pi = 3.14; //Adouble r = 1.0; //Bdouble area = pi * r * r; //C
上面三个操作的数据依赖关系如下图所示:
如上图所示,A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。因此在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序。下图是该程序的两种执行顺序:
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。
程序顺序规则
根据happens- before的程序顺序规则,上面计算圆的面积的示例代码存在三个happens- before关系:
COPYA happens- before B;B happens- before C;A happens- before C;
这里的第3个happens- before关系,是根据happens- before的传递性推导出来的。
这里A happens- before B,但实际执行时B却可以排在A之前执行(看上面的重排序后的执行顺序),如果A happens- before B,JMM并不要求A一定要在B之前执行。JMM仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。这里操作A的执行结果不需要对操作B可见;而且重排序操作A和操作B后的执行结果,与操作A和操作B按happens- before顺序执行的结果一致。在这种情况下,JMM会认为这种重排序并不非法(not illegal),JMM允许这种重排序。
在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽可能的开发并行度。编译器和处理器遵从这一目标,从happens- before的定义我们可以看出,JMM同样遵从这一目标。
重排序对多线程的影响
现在让我们来看看,重排序是否会改变多线程程序的执行结果。请看下面的示例代码:
COPYclass ReorderExample { int a = 0; boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } public void reader() { if (flag) { //3 int i = a * a; //4 …… } }}
flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入?
答案是:不一定能看到。
由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作1和操作2重排序时,可能会产生什么效果?请看下面的程序执行时序图:
如上图所示,操作1和操作2做了重排序。程序执行时,线程A首先写标记变量flag,随后线程B读这个变量。由于条件判断为真,线程B将读取变量a。此时,变量a还根本没有被线程A写入,在这里多线程程序的语义被重排序破坏了!
注:本文统一用红色的虚箭线表示错误的读操作,用绿色的虚箭线表示正确的读操作。
下面再让我们看看,当操作3和操作4重排序时会产生什么效果(借助这个重排序,可以顺便说明控制依赖性)。
下面是操作3和操作4重排序后,程序的执行时序图:
在程序中,操作3和操作4存在控制依赖关系。当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜测(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓存中。当接下来操作3的条件判断为真时,就把该计算结果写入变量i中。
从图中我们可以看出,猜测执行实质上对操作3和4做了重排序。重排序在这里破坏了多线程程序的语义!
在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。
顺序一致性
数据竞争与顺序一致性保证
当程序未正确同步时,就会存在数据竞争。java内存模型规范对数据竞争的定义如下:
- 在一个线程中写一个变量,
- 在另一个线程读同一个变量,
- 而且写和读没有通过同步来排序。
当代码中包含数据竞争时,程序的执行往往产生违反直觉的结果。如果一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序。
JMM对正确同步的多线程程序的内存一致性做了如下保证:
如果程序是正确同步的,程序的执行将具有顺序一致性(sequentially consistent)–即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同(马上我们将会看到,这对于程序员来说是一个极强的保证)。这里的同步是指广义上的同步,包括对常用同步原语(lock,volatile和final)的正确使用。
顺序一致性内存模型
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证。顺序一致性内存模型有两大特性:
- 一个线程中的所有操作必须按照程序的顺序来执行。
- (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
顺序一致性内存模型为程序员提供的视图如下:
在概念上,顺序一致性模型有一个单一的全局内存,这个内存通过一个左右摆动的开关可以连接到任意一个线程。同时,每一个线程必须按程序的顺序来执行内存读/写操作。从上图我们可以看出,在任意时间点最多只能有一个线程可以连接到内存。当多个线程并发执行时,图中的开关装置能把所有线程的所有内存读/写操作串行化。
为了更好的理解,下面我们通过两个示意图来对顺序一致性模型的特性做进一步的说明。
假设有两个线程A和B并发执行。其中A线程有三个操作,它们在程序中的顺序是:A1->A2->A3。B线程也有三个操作,它们在程序中的顺序是:B1->B2->B3。
假设这两个线程使用监视器来正确同步:A线程的三个操作执行后释放监视器,随后B线程获取同一个监视器。那么程序在顺序一致性模型中的执行效果将如下图所示:
现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图:
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:B1->A1->A2->B2->A3->B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
但是,在JMM中就没有这个保证。未同步程序在JMM中不但整体的执行顺序是无序的,而且所有线程看到的操作执行顺序也可能不一致。比如,在当前线程把写过的数据缓存在本地内存中,且还没有刷新到主内存之前,这个写操作仅对当前线程可见;从其他线程的角度来观察,会认为这个写操作根本还没有被当前线程执行。只有当前线程把本地内存中写过的数据刷新到主内存之后,这个写操作才能对其他线程可见。在这种情况下,当前线程和其它线程看到的操作执行顺序将不一致。
同步程序的顺序一致性效果
下面我们对前面的示例程序ReorderExample用监视器来同步,看看正确同步的程序如何具有顺序一致性。
请看下面的示例代码:
COPYclass SynchronizedExample { int a = 0; boolean flag = false; public synchronized void writer() { a = 1; flag = true; } public synchronized void reader() { if (flag) { int i = a; …… } }}
上面示例代码中,假设A线程执行writer()方法后,B线程执行reader()方法。这是一个正确同步的多线程程序。根据JMM规范,该程序的执行结果将与该程序在顺序一致性模型中的执行结果相同。下面是该程序在两个内存模型中的执行时序对比图:
在顺序一致性模型中,所有操作完全按程序的顺序串行执行。而在JMM中,临界区内的代码可以重排序(但JMM不允许临界区内的代码“逸出”到临界区之外,那样会破坏监视器的语义)。JMM会在退出监视器和进入监视器这两个关键时间点做一些特别处理,使得线程在这两个时间点具有与顺序一致性模型相同的内存视图(具体细节后文会说明)。虽然线程A在临界区内做了重排序,但由于监视器的互斥执行的特性,这里的线程B根本无法“观察”到线程A在临界区内的重排序。这种重排序既提高了执行效率,又没有改变程序的执行结果。
从这里我们可以看到JMM在具体实现上的基本方针:在不改变(正确同步的)程序执行结果的前提下,尽可能的为编译器和处理器的优化打开方便之门。
未同步程序的执行特性
对于未同步或未正确同步的多线程程序,JMM只提供最小安全性:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false),JMM保证线程读操作读取到的值不会无中生有(out of thin air)的冒出来。为了实现最小安全性,JVM在堆上分配对象时,首先会清零内存空间,然后才会在上面分配对象(JVM内部会同步这两个操作)。因此,在以清零的内存空间(pre-zeroed memory)分配对象时,域的默认初始化已经完成了。
JMM不保证未同步程序的执行结果与该程序在顺序一致性模型中的执行结果一致。因为未同步程序在顺序一致性模型中执行时,整体上是无序的,其执行结果无法预知。保证未同步程序在两个模型中的执行结果一致毫无意义。
和顺序一致性模型一样,未同步程序在JMM中的执行时,整体上也是无序的,其执行结果也无法预知。同时,未同步程序在这两个模型中的执行特性有下面几个差异:
- 顺序一致性模型保证单线程内的操作会按程序的顺序执行,而JMM不保证单线程内的操作会按程序的顺序执行(比如上面正确同步的多线程程序在临界区内的重排序)。这一点前面已经讲过了,这里就不再赘述。
- 顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证所有线程能看到一致的操作执行顺序。这一点前面也已经讲过,这里就不再赘述。
- JMM不保证对64位的long型和double型变量的读/写操作具有原子性,而顺序一致性模型保证对所有的内存读/写操作都具有原子性。
第3个差异与处理器总线的工作机制密切相关。在计算机中,数据通过总线在处理器和内存之间传递。每次处理器和内存之间的数据传递都是通过一系列步骤来完成的,这一系列步骤称之为总线事务(bus transaction)。总线事务包括读事务(read transaction)和写事务(write transaction)。读事务从内存传送数据到处理器,写事务从处理器传送数据到内存,每个事务会读/写内存中一个或多个物理上连续的字。这里的关键是,总线会同步试图并发使用总线的事务。在一个处理器执行总线事务期间,总线会禁止其它所有的处理器和I/O设备执行内存的读/写。下面让我们通过一个示意图来说明总线的工作机制:
如上图所示,假设处理器A,B和C同时向总线发起总线事务,这时总线仲裁(bus arbitration)会对竞争作出裁决,这里我们假设总线在仲裁后判定处理器A在竞争中获胜(总线仲裁会确保所有处理器都能公平的访问内存)。此时处理器A继续它的总线事务,而其它两个处理器则要等待处理器A的总线事务完成后才能开始再次执行内存访问。假设在处理器A执行总线事务期间(不管这个总线事务是读事务还是写事务),处理器D向总线发起了总线事务,此时处理器D的这个请求会被总线禁止。
总线的这些工作机制可以把所有处理器对内存的访问以串行化的方式来执行;在任意时间点,最多只能有一个处理器能访问内存。这个特性确保了单个总线事务之中的内存读/写操作具有原子性。
在一些32位的处理器上,如果要求对64位数据的写操作具有原子性,会有比较大的开销。为了照顾这种处理器,java语言规范鼓励但不强求JVM对64位的long型变量和double型变量的写具有原子性。当JVM在这种处理器上运行时,会把一个64位long/ double型变量的写操作拆分为两个32位的写操作来执行。这两个32位的写操作可能会被分配到不同的总线事务中执行,此时对这个64位变量的写将不具有原子性。
当单个内存操作不具有原子性,将可能会产生意想不到后果。请看下面示意图:
如上图所示,假设处理器A写一个long型变量,同时处理器B要读这个long型变量。处理器A中64位的写操作被拆分为两个32位的写操作,且这两个32位的写操作被分配到不同的写事务中执行。同时处理器B中64位的读操作被分配到单个的读事务中执行。当处理器A和B按上图的时序来执行时,处理器B将看到仅仅被处理器A“写了一半“的无效值。
注意,在JSR -133之前的旧内存模型中,一个64位long/ double型变量的读/写操作可以被拆分为两个32位的读/写操作来执行。从JSR -133内存模型开始(即从JDK5开始),仅仅只允许把一个64位long/ double型变量的写操作拆分为两个32位的写操作来执行,任意的读操作在JSR -133中都必须具有原子性(即任意读操作必须要在单个读事务中执行)。
本文由
传智教育博学谷狂野架构师
教研团队发布。如果本文对您有帮助,欢迎
关注
和点赞
;如果您有任何建议也可留言评论
或私信
,您的支持是我坚持创作的动力。转载请注明出处!
-
环球即时看!查看Linux系统下CPU、内存、硬盘等信息
做个记录,方便日后查看使用。1 查看CPU1 1查看CPU个数[root@qy-ggyf-zyl-31~] cat proc cpuinfo|grep"physical
来源: -
环球短讯!第121篇: DOM常用类型(Document、Element)
好家伙,本篇为《JS高级程序设计》第十四章“DOM编程”学习笔记1 Document类型Document类型是JavaScrip...
来源: 通讯!98%的程序员,都没有研究过JVM重排序和顺序一致性
环球即时看!查看Linux系统下CPU、内存、硬盘等信息
环球短讯!第121篇: DOM常用类型(Document、Element)
全球实时:CountDownLatch的使用
世界速讯:鹏都农牧:缅甸肉牛暂无法通关,公司正在积极推进复工复产工作
天天看点:10岁小学生玩游戏18天花掉14万 退款申请腾讯无视:家长感慨一定看好支付密码
焦点精选!摩托罗拉Razr 2023外观曝光:超大外屏抢眼
全球微头条丨安卓都16GB内存了 iPhone 15 Pro还在挤牙膏
【时快讯】或6.58万起 疑似比亚迪海鸥售价曝光:最远能跑405km
中国香港影史首部票房破亿华语电影诞生 此前TOP10均为好莱坞
每日快看:【黑科技】GPS北斗卫星授时技术下的NTP网络时间服务器
世界热头条丨每天走路10000步很健康?错 大错特错!
踢翻日系!比亚迪又要对BBA动手了
当前报道:20年来最低点!PC出货量暴跌:Intel等巨头业绩“凉凉” 显卡存储也没人买
SpringCloud+Dubbo3 = 王炸 !
世界微动态丨理想汽车:被4S店PUA、不如看看直营无套路零售中心
网红玩具有危险!2岁幼童误吞28颗磁力珠 医生提醒:必须及时取出
叫板特斯拉 保时捷:单踏板模式制动能量回收效率不高
座位被占 女子爬火车行李架睡觉:千万不要模仿
环球资讯:WiFi万能钥匙联合长安汽车,“一键连接”拓展至车机端
木鱼cms系统审计小结
世界资讯:单调栈
灵魂摆渡的作者真的杀了他的妻子吗?灵魂摆渡的经典台词有哪些?
董明珠回应直播带货争议:不是为了卖产品 是推广技术
当前关注:12万就能上太空!日企将用高空气球开启太空旅游
【天天播资讯】昔日国内共享单车巨头!ofo小黄车被曝无法登录:超1600万人押金没退
“AI飞行员”成功驾驶战斗机 美国顶尖飞行员曾是手下败将
播报:暴雪公布《暗黑破坏神4》实机画面:有《暗黑2》那味了
六一儿童节的优美句子有哪些?关于六一儿童节的作文汇总
移动空调效果如何选择?移动空调哪个牌子好又实惠省电?
迅雷崩溃是怎么回事?迅雷崩溃怎么解决?
家用数码相机怎么选?家用数码相机什么牌子最好?
三星S8000c什么时候上市的?三星s8000c功能介绍
直播预告 | 企业如何轻松完成数据治理?火山引擎 DataLeap 给你一份实战攻略!
分层测试(五):端到端测试
热文:李楠谈网暴 在线教网友“如何避免成为网暴的施暴者”
要闻速递:终于告别“板砖”适配器!联想拯救者Y9000P 2023将支持140W便携快充
499元 雷蛇炼狱蝰蛇V3专业版发布:59克超轻量化设计
融资客最看好个股一览
【码农教程】手把手教你学会Mockito使用
世界热门:用一个例子学会适配器设计模式
天天视点!量化工具篇
世界播报:2.概率论
视焦点讯!(数据库系统概论|王珊)第六章关系数据理论-第二节:规范化
别乱买!三大要素教你如何选对吸尘器
环球看点!一次看个够!《原子之心》双子舞伶所有场景合集
世界时讯:微软为Chrome官网注入大幅广告:只为将用户留在Edge
天天热资讯!微软收购动视暴雪获新盟友!NVIDIA态度转变签署10年游戏协议
热门:读Java实战(第二版)笔记17_反应式编程
每日播报!若若_关于若若的简介
焦点速读:长沙凌晨1点马路人流量惊人 像在倒时差:网友称想去打卡 当地人回应
全球最资讯丨8GB来了!iPhone 15内存大升级:苹果区别对待 想要花万元买Pro版
环球热头条丨荣耀Magic5 Lite获DXO电池性能第一名 续航可超三天
雷克萨斯、英菲尼迪、凯迪拉克等豪车中国市场暴跌:国人不当冤大头
今日聚焦!面试官:怎么去除 List 中的重复元素?我一行代码搞定,赶紧拿去用!
热文:OPPO打造!唯一配备潜望长焦的天玑旗舰来了
天天短讯!死磕RTX 40!AMD RX 7600/7700/7800齐曝光:据说苏妈定价有诚意
重现《放羊的星星》!林志颖特斯拉车祸后复出晒照 事故原因成谜 是踩错刹车?
Map数据结构详解
环球视讯!在PHP和JavaScript中设置Cookie、会话存储(SessionStorage)和本地存储(LocalStorage)
天天百事通!今日成都到康定怎么坐车_成都到康定
亮机卡也有春天 锐龙7000核显超频到3.1GHz 游戏性能猛增40%
当前要闻:困扰十几亿人!脚趾甲咋会向肉里长?
世界焦点!TCL发布超薄四开门冰箱T9:0cm无缝式嵌入 456L仅3399元
增程、换电、800V高压快充 谁才是未来新能源车最佳补能方式?
环球新动态:发3000元却收回2800元!“慈善主播”被行拘 账号被封
【焦点热闻】秘而不宣的读法
C#的string是一种糟糕的设计吗?
当前视讯!队列——queue的用法(及洛谷B3616)
vue-cli安装依赖 props属性三种方式 混入迷信 插件 elementUI vuex vue Routerd localStorage系列
openfoam文件读取
fusion app自定义事件源码介绍(上)
世界热门:RTX 4070 Laptop逆天能效比!七彩虹将星X15 AT 2023游戏本首发评测
全球快看:1月豪华车销量榜:蔚来碾压全系合资二线豪华品牌
被网友玩坏?微软Bing的ChatGPT被证实变愚蠢了
每日消息!“张伟”骗取数位宝马车主百万购车款 4S店疯狂推卸责任?
AMD锐龙7 7735HS迷你机也有“青春版”:只变了两个USB接口
环球今头条!攒台白色的MATX主机,在B760主板上也能玩好内存超频
全球焦点!RS485 MODBUS转PROFINET网关案例 | 超声波明渠流量计接入到PLC1200 PROFINE
焦点热门:前后端分离项目解决跨域的终极方法
环球今热点:[Java基础]自动装箱与自动拆箱--为什么整型比较必须用equals?
【天天新要闻】Detecting glass in Simulataneous Localisation and Mapping
天天热文:贝叶斯与卡尔曼滤波(2)--连续随机变量的贝叶斯公式
当前热门:用送的那块布擦镜片:小心眼镜被废!
观热点:60岁快递员意外猝死 快递公司回应:深感痛心 善后已达成一致
女子连刷10个差评商家找上门:不好吃你天天来干嘛 谁生活容易啊
【世界报资讯】果粉入手一加Ace 2:开20个应用不杀后台 苹果开3个应用就不行了
80后回忆的“均瑶牛奶”公司进军新能源车:首款纯电SUV云兔来了 莆田生产
信息:Qt调用摄像头一,基础版
k8s多节点二进制部署以及Dashboard UI
记住这12个要点,你也能打造出让HR和技术主管前一亮的前端简历
《原子之心》冰箱诺拉全九国语言配音:怎么没日语?
粉丝发现周深自用手机是iQOO 11 Pro传奇版:5米开外就能看到
电动两轮车总是骑半路就没电?这5种错误充电习惯赶快纠正
世界今头条!男子酷爱嚼槟榔:最终确诊舌癌
【报资讯】免费的ChatGPT意外断网 国内伪装自主的AI露馅了
守护安全|AIRIOT城市天然气综合管理解决方案
0x03_My-OS在实体机上面运行
每日播报!【算法训练营day53】LeetCode1143. 最长公共子序列 LeetCode1035. 不相交的线 LeetCode53. 最大子序和
快讯:两数之和、三数之和、四数之和(双指针)