浅谈Spring循环依赖
文章目录
- 1.前言
- 2.什么是循环依赖?
- 3.两种Spring容器循环依赖
- 3.1.构造器循环依赖(无法解决)
- 3.2.setter循环依赖(可以解决)
- 3.3.小结
- 4.循环依赖检查
- 5.循环依赖的处理
- 5.1.单例setter循环依赖
- 5.2.Spring解决循环依赖
- 5.3. 循环依赖的本质
- 5.4.what?问题的本质居然是two sum!
- 5.5.单例构造器注入循环依赖
- 5.6.原型模式循环依赖
- 6.总结
- 6.1.循环依赖的原因
- 6.2.循环依赖的解决方案
- 6.3.整个从创建bean到解决循环依赖的过程
- 6.4.循环依赖建议
- 7.Spring循环依赖图解
1.前言
想彻底弄清楚spring的循环依赖问题,首先得弄清楚
- 循环依赖是如何发生的
- spring又是如何检测循环依赖的发生的。
- 其次再探究spring如何解决循环依赖的问题
2.什么是循环依赖?
循环依赖就是循环引用,指两个或多个bean互相持有对方,比如说TestA引用TestB、TestB引用TestA,最终形成一个闭环。
注意:循环依赖不是指循环调用。
循环调用:指方法之间的环调用,循环调用是无解的,除非有终结条件,否则就是死循环,最终会导致内存溢出异常。
package com.bruce.test;import java.io.File;public class TestFile {static int count=0;public static void main(String[] args) {File f=new File("D:\\");showFiles(f);System.out.println("文件总数是:"+count);}public static void showFiles(File f){//获取所有的文件和文件目录File[] files = f.listFiles();for (File file : files) {if(file.isFile()){if(file.getName().endsWith(".jpg")){count++;System.out.println(file.getAbsolutePath());}}else{//目录showFiles(file);}}}
}
3.两种Spring容器循环依赖
- 构造器循环依赖
- setter方法循环依赖
就是A类里面有B类的引用b,B类里面有C类的引用c,C类里面有A类的引用a,如下图所示
3.1.构造器循环依赖(无法解决)
A类
package com.bruce.spring2021;public class A {private B b;public A (B b){this.b = b;}public B getB() {return b;}public void setB(B b) {this.b = b;}
}
B类
package com.bruce.spring2021;public class B {private C c;public B (C c){this.c = c;}public C getC() {return c;}public void setC(C c) {this.c = c;}
}
C类
package com.bruce.spring2021;public class C {private A a;public C (A a){this.a = a;}public A getA() {return a;}public void setA(A a) {this.a = a;}
}
applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="A" class="com.bruce.spring2021.A" ><constructor-arg name="b" ref="B"/></bean><bean id="B" class="com.bruce.spring2021.B" ><constructor-arg name="c" ref="C"/></bean><bean id="C" class="com.bruce.spring2021.C" ><constructor-arg name="a" ref="A"/></bean></beans>
主函数入口
package com.bruce.spring2021;import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestSpring {@Testpublic void test1(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");}}
运行main函数,报错截图
3.2.setter循环依赖(可以解决)
A类
public class A {public A() {}private B b;public B getB() {return b;}public void setB(B b) {this.b = b;}
}
B类
public class B {public B() {}private C c;public C getC() {return c;}public void setC(C c) {this.c = c;}
}
C类
public class C {public C() {}private A a;public A getA() {return a;}public void setA(A a) {this.a = a;}
}
applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="A" class="com.bruce.spring2021.A" ><property name="b" ref="B"/></bean><bean id="B" class="com.bruce.spring2021.B" ><property name="c" ref="C"/></bean><bean id="C" class="com.bruce.spring2021.C" ><property name="a" ref="A"/></bean></beans>
最后输出
package com.bruce.spring2021;import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestSpring {@Testpublic void test2(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");A a = ctx.getBean("A", A.class);//System.out.println(a);//System.out.println(a.getB());//System.out.println(a.getB().getC());//System.out.println(a.getB().getC().getA());}
}
3.3.小结
通常来说,如果问Spring内部如何解决循环依赖,一定是单默认的单例Bean中,属性互相引用的场景。比如几个Bean之间的互相引用:
甚至自己“循环”依赖自己:
先说明前提:原型(Prototype
)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory
类中下面的判断,抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);
}
原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A…
这就套娃了, 你猜是先StackOverflow
还是OutOfMemory
?
Spring怕你不好猜,就先抛出了BeanCurrentlyInCreationException
基于构造器的循环依赖,就更不用说了,官方文档都摊牌了,你想让构造器注入支持循环依赖,是不存在的,不如把代码改了。
那么默认单例的属性注入场景,Spring是如何支持循环依赖的?
4.循环依赖检查
<bean id="a" class="A"><property name="b" ref="b">
<bean/><bean id="b" class="B"><property name="a" ref="a">
<bean/>
无论单例还是原型模式(下文①代表图中步骤1),spring都有对应的集合(singletonsCurrentlyInCreation
)保存当前正在创建的beanName,标识该beanName正在被创建。
在bean创建前,
①检测当前bean是否在创建中
,如果不在创建中则
②将beanName加入集合,往下创建bean。在bean创建前,检测到当前的bean正在创建
,则说明发生循环依赖,抛出异常。最后记得当bean创建完时将beanName移出集合
。
5.循环依赖的处理
5.1.单例setter循环依赖
spring注入属性的方式有多种,但是只有一种循环依赖能被解决:单例setter依赖注入
。
spring解决循环依赖的做法是未等bean创建完就先将实例曝光出去,方便其他bean的引用。同时还提到了三级缓存,最先曝光到第三级缓存singletonFactories中。简单的说,就是spring先将创建好的实例放到缓存中,让其他bean可以提前引用到该对象。
示例
// 第一种 注解方式
public class A {@Autowiredprivate B b;
}public class B {@Autowiredprivate A a;
}// ===========================
// 第二种 xml配置方式
public class A {private B b;// getter setter
}public class B {private A a;// getter setter
}<bean id="a" class="A"><property name="b" ref="b">
<bean/><bean id="b" class="B"><property name="a" ref="a">
<bean/>
分析
其中跟循环依赖检测对比,新添加的几个关键节点已经用黄色标识出来,这里有几个重点给大家画一下。
-
提前曝光,如果用c语言的说法就是将指针曝光出去,用java就是将引用对象曝光出去。也就是说即便a对象还未创建完成,但是在④实例化过程中new A()动作已经开辟了一块内存空间,只需要将该地址抛出去b就可以引用的到,而不管a后期还会进行初始化等其他操作
-
已经了解了提前曝光的作用,而相比而言⑤曝光的时机也非常的重要,该时机发生在④实例化之后,⑥填充与⑯ 初始化之前。spring循环依赖之所以不能解决实例化注入的原因正式因为注入时机在曝光之前所导致
5.2.Spring解决循环依赖
首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。
在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map:
singletonObjects
它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。singletonFactories
映射创建Bean的原始工厂earlySingletonObjects
映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance.
后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。
为什么成为后两个Map为垫脚石,假设最终放在singletonObjects
的Bean是你想要的一杯“凉白开”。
那么Spring准备了两个杯子,即singletonFactories
和earlySingletonObjects
来回“倒腾”几番,把热水晾成“凉白开”放到singletonObjects
中。
闲话不说,都浓缩在图里。
5.3. 循环依赖的本质
上文了解完Spring如何处理循环依赖之后,让我们跳出“阅读源码”的思维,假设让你实现一个有以下特点的功能,你会怎么做?
- 将指定的一些类实例为单例
- 类中的字段也都实例为单例
- 支持循环依赖
举个例子,假设有类A:
@Component
public class A {@Autowiredprivate B b;
}
// 类B:@Component
public class B {@Autowiredprivate A a;@Transactionpublic void show(){}
}
说白了让你模仿Spring:假装A和B是被@Component
修饰, 并且类中的字段假装是@Autowired
修饰的,处理完放到Map中。其实非常简单,笔者写了一份粗糙的代码,可供参考:
/*** 放置创建好的bean Map*/private static Map<String, Object> cacheMap = new HashMap<>(2);public static void main(String[] args) {// 假装扫描出来的对象Class[] classes = {A.class, B.class};// 假装项目初始化实例化所有beanfor (Class aClass : classes) {getBean(aClass);}// checkSystem.out.println(getBean(B.class).getA() == getBean(A.class));System.out.println(getBean(A.class).getB() == getBean(B.class));}@SneakyThrowsprivate static <T> T getBean(Class<T> beanClass) {// 本文用类名小写 简单代替bean的命名规则String beanName = beanClass.getSimpleName().toLowerCase();// 如果已经是一个bean,则直接返回if (cacheMap.containsKey(beanName)) {return (T) cacheMap.get(beanName);}// 将对象本身实例化Object object = beanClass.getDeclaredConstructor().newInstance();// 放入缓存cacheMap.put(beanName, object);// 把所有字段当成需要注入的bean,创建并注入到当前bean中Field[] fields = object.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);// 获取需要注入字段的classClass<?> fieldClass = field.getType();String fieldBeanName = fieldClass.getSimpleName().toLowerCase();// 如果需要注入的bean,已经在缓存Map中,那么把缓存Map中的值注入到该field即可// 如果缓存没有 继续创建field.set(object, cacheMap.containsKey(fieldBeanName)? cacheMap.get(fieldBeanName) : getBean(fieldClass));}// 属性填充完成,返回return (T) object;}
这段代码的效果,其实就是处理了循环依赖,并且处理完成后,cacheMap中放的就是完整的“Bean”了
这就是“循环依赖”的本质,而不是“Spring如何解决循环依赖”。
5.4.what?问题的本质居然是two sum!
看完笔者刚才的代码有没有似曾相识?没错,和two sum的解题是类似的。不知道two sum是什么梗的,笔者和你介绍一下:two sum是刷题网站leetcode序号为1的题,也就是大多人的算法入门的第一题。常常被人调侃,有算法面的公司,被面试官钦定了,合的来。那就来一道two sum走走过场。
问题内容是:给定一个数组,给定一个数字。返回数组中可以相加得到指定数字的两个索引。比如:给定
nums = [2, 7, 11, 15],
target = 9 那么要返回 [0, 1],因为2 + 7 = 9这道题的优解是,一次遍历+HashMap:
class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {int complement = target - nums[i];if (map.containsKey(complement)) {return new int[] { map.get(complement), i };}map.put(nums[i], i);}throw new IllegalArgumentException("No two sum solution");}
}
先去Map中找需要的数字,没有就将当前的数字保存在Map中,如果找到需要的数字,则一起返回。
和笔者上面的代码是不是一样?
先去缓存里找Bean,没有则实例化当前的Bean放到Map,如果有需要依赖当前Bean的,就能从Map取到。
5.5.单例构造器注入循环依赖
上面已经剧透了这个方式是不得行的,原因是依赖注入的时间点不对,他的依赖注入发生在构造器阶段,这个时候连实例都没有,内存都还没开辟完,当然也还没有进行提前曝光,因此不得行
示例
public class A {private B b;@Autowiredpublic A(B b) {this.b = b;}
}public class B {private A a;@Autowiredpublic B(A a) {this.a = a}
}
分析
图上重点地方也用黄色标出了,问题的原因处在④实例化,实例化的过程是调用new A(B b);的过程,这时的A还未创建出来,根本是不可能提前曝光的,正是这个原因导致⑨无法获取到三级缓存,进而导致⑩异常的抛出
5.6.原型模式循环依赖
这此没有图了,因为原型模式每次都是重新生成一个全新的bean,根本没有缓存一说。这将导致实例化A完,填充发现需要B,实例化B完又发现需要A,而每次的A又都要不一样,所以死循环的依赖下去。唯一的做法就是利用循环依赖检测,发现原型模式下存在循环依赖并抛出异常.
6.总结
6.1.循环依赖的原因
beanA依赖beanB,beanB依赖beanA,导致两者都不能被创建,发生在填充属性的环节
6.2.循环依赖的解决方案
提前曝光机制+三级缓存
- 提前曝光
正常来说bean的创建过程有三步:实例化
->填充属性
->初始化
,提前曝光就是实例化后填充属性前将bean放入缓存
总结一下循环依赖,spring只能解决setter注入单例模式下的循环依赖问题。要想解决循环依赖必须要满足2个条件:
- 需要用于
提前曝光
的缓存 - 属性的
注入时机
必须发生在提前曝光
动作之后,不管是填充
还是初始化
都行,总之不能在实例化,因为提前曝光动作在实例化之后
理解了这2点就可以轻松驾驭循环依赖了。比如构造器注入是不满足第二个条件,曝光时间不对。而原型模式则是缺少了第一个条件,没有提前曝光的缓存供使用。
- 三级缓存
singletonObjects:一级缓存,用于存放完全初始化好的bean,从改缓存中取出的bean可以直接使用
earlySingletonObjects:二级缓存,用于存放提前曝光的单例对象的cache,存放原始的bean对象(尚未填充属性)
singletoneFactories:三级缓存,存放提前曝光的bean的工厂,用于生产二级缓存提前曝光的实例urrentlyInCreation:正在创建的bean集合,在bean开始创建时放值,创建完成时移出
alreadyCreated:bean被创建完成后,会放进这个set集合
先从一级缓存获取bean
->从二级缓存获取
->如果工厂可以创建bean,就从三级缓存获取
,并且将三级缓存中的bean移到二级缓存
public interface ObjectFactory<T> {T getObject() throws BeansException;
}
加入singletonFactories
三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
6.3.整个从创建bean到解决循环依赖的过程
context.getBean(A.class)->实例化->放入缓存->依赖注入B->getBean(B)->实例化B并放入缓存->B依赖注入A->getBean(A)获取到了缓存中的值并正常返回->B初始化成功->A初始化成功
6.4.循环依赖建议
业务代码中尽量不要使用构造器注入,三级缓存解决不了构造器循环依赖的问题
业务代码中为了简洁,尽量使用field注入而不是setter方法注入
7.Spring循环依赖图解
相关文章:

浅谈Spring循环依赖
文章目录1.前言2.什么是循环依赖?3.两种Spring容器循环依赖3.1.构造器循环依赖(无法解决)3.2.setter循环依赖(可以解决)3.3.小结4.循环依赖检查5.循环依赖的处理5.1.单例setter循环依赖5.2.Spring解决循环依赖5.3. 循环…...
华为OD机试题 - 拼接 URL(JavaScript)| 包含代码编写思路
最近更新的博客 华为OD机试题 - 字符串加密(JavaScript) 华为OD机试题 - 字母消消乐(JavaScript) 华为OD机试题 - 字母计数(JavaScript) 华为OD机试题 - 整数分解(JavaScript) 华为OD机试题 - 单词反转(JavaScript) 华为OD机试题 最近更新的博客使用说明拼接 URL题目…...
【FFMPEG】Filtering Introduction[翻译/举例]
Filtering Introduction Filtering in FFmpeg is enabled through the libavfilter library. FFmpeg中的Filtering可以通过libavfilter library来使用。 In libavfilter, a filter can have multiple inputs and multiple outputs. To illustrate the sorts of things that are…...

什么是IP65?仅仅是防水等级吗?看完本文直呼666!
IP65在硬件设备,准确的来说在电气设备中,这个参数很常见,但是作为网络技术的博主,为啥要介绍IP65? 这个很好解释,因为网络设备,比如路由器、交换机,还有服务器、监控等都是属于电气…...
Flask入门(10):数据库连接池
目录10.数据库连接池模式一模式二示例:使用数据库连接池进行登录验证10.数据库连接池 参考:https://www.cnblogs.com/wangkun122/articles/8992637.html 通过DBUtils实现数据库连接池 安装: pip install DBUtils1.2注意:pytho…...

华为OD机试C++实现 - 最小步骤数
最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...
数仓:用户行为类指标一网打尽
前言 用户行为分析是对用户在产品或触点上产生的行为及行为背后的数据进行分析,通过构建用户行为数据分析体系或者用户画像,来改变产品、营销、运营决策,实现精细化运营,指导业务增长。总之,很重要。 先来看下用户类…...
mysql数据库的主从复制
一、实现主从复制的方式。 异步复制:它是mysql默认的同步方式,从库通过io线程去拉取 bin log时,主库不需要关注这个时候是否有从库在同步数据,他只做自己的事情就可以了, 整个复制过程都是异步完成的 ; 半同步复制&…...

【极海APM32替代笔记】低功耗模式、WFI命令等进入不了休眠的可能原因(系统定时器SysTick一直产生中断)
【极海APM32替代笔记】低功耗模式、WFI命令等进入不了休眠的可能原因(系统定时器SysTick一直产生中断) 【STM32笔记】低功耗模式配置及避坑汇总 前文: blog.csdn.net/weixin_53403301/article/details/128216064 【STM32笔记】HAL库低功耗模…...

一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言 秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。 知识点详解 秒杀系统架构图 秒杀流程图 秒杀系统设计 这篇文章一万多字,…...

华为OD机试真题 用 C++ 实现 - 子序列长度 | 多看题,提高通过率
最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...
华为OD机试题 - 符合条件的子串长度(JavaScript)| 包含代码编写思路
最近更新的博客 华为OD机试题 - 字符串加密(JavaScript) 华为OD机试题 - 字母消消乐(JavaScript) 华为OD机试题 - 字母计数(JavaScript) 华为OD机试题 - 整数分解(JavaScript) 华为OD机试题 - 单词反转(JavaScript) 华为OD机试题 最近更新的博客使用说明符合条件的子…...

快速读懂网络拓扑图
快速读懂网络拓扑图几重常见的网络拓扑总线型拓扑简介优点缺点环型拓扑简介优点缺点星型拓扑简介优点缺点网络层级机构节点结点链路通路不同的连接线代表什么意思?不同颜色、粗细的直线代表什么意思?闪电线-串行链路几重常见的网络拓扑 总线型拓扑 简介…...
《上海市创新型企业总部认定和奖励管理办法》
各区人民政府、有关单位: 为加快推动上海创新型经济发展,支持各类高成长性企业和研发机构升级打造创新型企业总部,培育壮大更多高能级创新主体,为建设具有全球影响力的科技创新中心提供支撑,现将《上海市创新型企业总…...

LeetCode 160. 相交链表 -- 消除长度差
相交链表 简单 2K 相关企业 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意…...

《分布式技术原理与算法解析》学习笔记Day19
分布式通信:消息队列 什么是消息队列? 队列是一种具有先进先出特点的数据结构,消息队列是基于队列实现的、存储具有特定格式的消息数据。消息以特定格式放入这个队列的尾部后直接返回,不需要系统马上处理,之后有其他…...

云、安全、网络三位一体,Akamai 推出大规模分布式边缘和云平台 Akamai Connected Cloud
出品 | CSDN 云计算 云服务市场规模在持续增长。 基于网络技术积累与优势,与布局边缘计算之后,巨头 Akamai 在继续推进它的技术与产品进程。近日,Akamai 正式推出大规模分布式边缘和云平台 Akamai Connected Cloud,包含云计算、安…...

生产者消费者模型(多线程工作)
目录 1.模型前提 2.阻塞队列(消费场所) 3. 实验 4.有关效率 1.模型前提 以单生产者对单消费者为例子: 前提一:有一个缓冲区作为消费场所。 前提二:有两种功能不同的线程分别具有消费与生产的能力。 前提三&…...
InnoDB锁
1、共享排他锁 Shared and Exclusive Locks--共享锁(SLock),允许持有该锁的事务读取一行数据--排它锁(XLock),允许持有该锁的事务删除或者更新一行数据特性:--行级锁--如果一个事务持有当前行的…...

Java Stream、File、IO 超详细整理,适合新手入门
目录 Java Stream Java File Java IO Java Stream Java Stream 是 Java 8 中引入的一种新的抽象数据类型,它允许开发人员使用函数式编程的方式来处理集合数据。 使用 Java Stream 可以方便地进行过滤、映射、排序和聚合等操作。下面是一个简单的示例:…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...