Java八股文面试笔记整理(三)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Java八股文面试笔记整理(三)
并发漏标问题

比较先进的垃圾回收器都支持并发标记,即在标记过程中,用户线程仍然能工作。但这样带来一个新的问题,如果用户线程修改了对象引用,那么就存在漏标问题。例如:

  1. 如图所示标记工作尚未完成

image.png

  1. 用户线程同时在工作,断开了第一层 3、4 两个对象之间的引用,这时对于正在处理 3 号对象的垃圾回收线程来讲,它会将 4 号对象当做是白色垃圾

image.png

  1. 但如果其他用户线程又建立了 2、4 两个对象的引用,这时因为 2 号对象是黑色已处理对象了,因此垃圾回收线程不会察觉到这个引用关系的变化,从而产生了漏标

image.png

  1. 如果用户线程让黑色对象引用了一个新增对象,一样会存在漏标问题

image.png

因此对于并发标记而言,必须解决漏标问题,也就是要记录标记过程中的变化。有两种解决方法:

  1. Incremental Update 增量更新法,CMS 垃圾回收器采用
  • 思路是拦截每次赋值动作,只要赋值发生,被赋值的对象就会被记录下来,在重新标记阶段再确认一遍
  1. Snapshot At The Beginning,SATB 原始快照法,G1 垃圾回收器采用
  • 思路也是拦截每次赋值动作,不过记录的对象不同,也需要在重新标记阶段对这些对象二次处理
  • 新加对象会被记录
  • 被删除引用关系的对象也被记录
垃圾回收器 - Parallel GC
  • eden 内存不足发生 Minor GC,采用标记复制算法,需要暂停用户线程
  • old 内存不足发生 Full GC,采用标记整理算法,需要暂停用户线程
  • 注重吞吐量
垃圾回收器 - ConcurrentMarkSweep GC
  • 它是工作在 old 老年代,支持并发标记的一款回收器,采用并发清除算法
  • 并发标记时不需暂停用户线程
  • 重新标记时仍需暂停用户线程
  • 如果并发失败(即回收速度赶不上创建新对象速度),会触发 Full GC
  • 注重响应时间
垃圾回收器 - G1 GC
  • 响应时间与吞吐量兼顾
  • 划分成多个区域,每个区域都可以充当 eden,survivor,old, humongous,其中 humongous 专为大对象准备
  • 分成三个阶段:新生代回收、并发标记、混合收集
  • 如果并发失败(即回收速度赶不上创建新对象速度),会触发 Full GC
G1 回收阶段 - 新生代回收
  1. 初始时,所有区域都处于空闲状态

image.png

  1. 创建了一些对象,挑出一些空闲区域作为伊甸园区存储这些对象

image.png

  1. 当伊甸园需要垃圾回收时,挑出一个空闲区域作为幸存区,用复制算法复制存活对象,需要暂停用户线程

image.png

  1. 复制完成,将之前的伊甸园内存释image.png
  2. 随着时间流逝,伊甸园的内存又有不足

image.png

  1. 将伊甸园以及之前幸存区中的存活对象,采用复制算法,复制到新的幸存区,其中较老对象晋升至老年代

image.png

  1. 释放伊甸园以及之前幸存区的内存

image.png

G1 回收阶段 - 并发标记与混合收集
  1. 当老年代占用内存超过阈值后,触发并发标记,这时无需暂停用户线程

image.png

  1. 并发标记之后,会有重新标记阶段解决漏标问题,此时需要暂停用户线程。这些都完成后就知道了老年代有哪些存活对象,随后进入混合收集阶段。此时不会对所有老年代区域进行回收,而是根据暂停时间目标优先回收价值高(存活对象少)的区域(这也是 Gabage First 名称的由来)。

image.png

  1. 混合收集阶段中,参与复制的有 eden、survivor、old,下图显示了伊甸园和幸存区的存活对象复制

image.png

  1. 下图显示了老年代和幸存区晋升的存活对象的复制

image.png

  1. 复制完成,内存得到释放。进入下一轮的新生代回收、并发标记、混合收集

image.png

3-4.内存溢出(OutOfMemory-OOM)

要求
  • 能够说出几种典型的导致内存溢出的情况
典型情况
  • 误用线程池导致的内存溢出
  • 查询数据量太大导致的内存溢出
  • 动态生成类导致的内存溢出

3-5.类加载

要求
  • 掌握类加载阶段
  • 掌握类加载器
  • 理解双亲委派机制
类加载过程的三个阶段
  1. 加载
  1. 将类的字节码载入方法区,并创建类.class 对象(class对象放置在堆中)
  2. 如果此类的父类没有加载,先加载父类
  3. 加载是懒惰执行
  1. 链接
  1. 验证 – 验证类是否符合 Class 规范,合法性、安全性检查
  2. 准备 – 为 static 变量分配空间,设置默认值
  3. 解析 – 将常量池的符号引用解析为直接引用
  1. 初始化
  1. 静态代码块、static 修饰的变量赋值、static final 修饰的引用类型变量赋值,会被合并成一个 <cinit> 方法,在初始化时被调用
  2. static final 修饰的基本类型变量赋值,在链接阶段就已完成
  3. 初始化是懒惰执行
jdk 8 的类加载器
名称 加载哪的类 说明
Bootstrap ClassLoader JAVA_HOME/jre/lib 无法直接访问
Extension ClassLoader JAVA_HOME/jre/lib/ext 上级为 Bootstrap,显示为 null
Application ClassLoader classpath 上级为 Extension
自定义类加载器 自定义 上级为 Application
双亲委派机制

所谓的双亲委派,就是指优先委派上级类加载器进行加载,如果上级类加载器

  • 能找到这个类,由上级加载,加载后该类也对下级加载器可见
  • 找不到这个类,则下级类加载器才有资格执行加载

双亲委派的目的有两点

  1. 让上级类加载器中的类对下级共享(反之不行),即能让你的类能依赖到 jdk 提供的核心类
  2. 让类的加载有优先次序,保证核心类优先加载
对双亲委派的误解

下面面试题的回答是错误的

image.png

错在哪了?

  • 自己编写类加载器就能加载一个假冒的 java.lang.System 吗? 答案是不行。
  • 假设你自己的类加载器用双亲委派,那么优先由启动类加载器加载真正的 java.lang.System,自然不会加载假冒的
  • 假设你自己的类加载器不用双亲委派,那么你的类加载器加载假冒的 java.lang.System 时,它需要先加载父类 java.lang.Object,而你没有用委派,找不到 java.lang.Object 所以加载会失败
  • 以上也仅仅是假设。事实上操作你就会发现,自定义类加载器加载以 java. 打头的类时,会抛安全异常,在 jdk9 以上版本这些特殊包名都与模块进行了绑定,更连编译都过不了

3-6.四种引用

要求
  • 掌握四种引用
强引用
  1. 普通变量赋值即为强引用,如 A a = new A();
  2. 通过 GC Root 的引用链,如果强引用不到该对象,该对象才能被回收

image.png

软引用(SoftReference)
  1. 例如:SoftReference a = new SoftReference(new A());
  2. 如果仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存仍不足,再次回收时才会释放对象
  3. 软引用自身需要配合引用队列来释放
  4. 典型例子是反射数据

image.png

弱引用(WeakReference)
  1. 例如:WeakReference a = new WeakReference(new A());
  2. 如果仅有弱引用引用该对象时,只要发生垃圾回收,就会释放该对象
  3. 弱引用自身需要配合引用队列来释放
  4. 典型例子是 ThreadLocalMap 中的 Entry 对象

image.png

虚引用(PhantomReference)
  1. 例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);
  2. 必须配合引用队列一起使用,当虚引用所引用的对象被回收时,由 Reference Handler 线程将虚引用对象入队,这样就可以知道哪些对象被回收,从而对它们关联的资源做进一步处理
  3. 典型例子是 Cleaner 释放 DirectByteBuffer 关联的直接内存

image.png

3-7.finalize

要求
  • 掌握 finalize 的工作原理与缺点

finalize

  • 它是 Object 中的一个方法,如果子类重写它,垃圾回收时此方法会被调用,可以在其中进行资源释放和清理工作
  • 将资源释放和清理放在 finalize 方法中非常不好,非常影响性能,严重时甚至会引起 OOM,从 Java9 开始就被标注为 @Deprecated,不建议被使用了
finalize 原理
  1. 1.  对 finalize 方法进行处理的核心逻辑位于 java.lang.ref.Finalizer 类中,它包含了名为 unfinalized 的静态变量(双向链表结构),Finalizer 也可被视为另一种引用对象(地位与软、弱、虚相当,只是不对外,无法直接使用)
  2. 2.  当重写了 finalize 方法的对象,在构造方法调用之时,JVM 都会将其包装成一个 Finalizer 对象,并加入 unfinalized 链表中

image.png

  1. 3.  Finalizer 类中还有另一个重要的静态变量,即 ReferenceQueue 引用队列,刚开始它是空的。当对象可以被当作垃圾回收时,就会把这些对象对应的 Finalizer 对象加入此引用队列
  2. 4.  但此时对象还没法被立刻回收,因为 unfinalized -> Finalizer 这一引用链还在引用它嘛,为的是【先别着急回收啊,等我调完 finalize 方法,再回收】
  3. 5.  FinalizerThread 线程会从 ReferenceQueue 中逐一取出每个 Finalizer 对象,把它们从链表断开并真正调用 finallize 方法

image.png

  1. 由于整个 Finalizer 对象已经从 unfinalized 链表中断开,这样没谁能引用到它和对象,所以下次 gc 时就被回收了
finalize 缺点
  • 无法保证资源释放:FinalizerThread 是守护线程,代码很有可能没来得及执行完,线程就结束了

scss

复制代码

setDameon(true);
  • 无法判断是否发生错误:执行 finalize 方法时,会吞掉任意异常(Throwable)

arduino

复制代码

try{
  //执行业务代码  ...}catch{  //空 捕捉异常后不执行任何操作}
  • 内存释放不及时:重写了 finalize 方法的对象在第一次被 gc 时,并不能及时释放它占用的内存,因为要等着 FinalizerThread 调用完 finalize,把它从 unfinalized 队列移除后,第二次 gc 时才能真正释放内存
  • 有的文章提到【Finalizer 线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的CPU时间较少,因此它永远也赶不上主线程的步伐】这个显然是错误的,FinalizerThread 的优先级较普通线程更高(8),永远也赶不上主线程的步伐的原因应该是 finalize 串行执行慢等综合导致

4-Java框架

4-1.Spring refresh 流程

要求
  • 掌握 refresh 的 12 个步骤
Spring refresh 概述

refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作。它的内部主要会调用 12 个方法,把它们称为 refresh 的 12 个步骤:

  1. prepareRefresh

做好准备工作

  1. obtainFreshBeanFactory

创建或获取BeanFactory

  1. prepareBeanFactory

准备BeanFactory

  1. postProcessBeanFactory

准备给子类扩展BeanFactory 该模式为模版设计模式

  1. invokeBeanFactoryPostProcessors

后处理器扩展BeanFactory

  1. registerBeanPostProcessors

准备Bean后处理器

  1. initMessageSource

为ApplicationContext提供国际化功能

  1. initApplicationEventMulticaster

为ApplicationContext提供事件发布器

  1. onRefresh

留给子类扩展 该模式为模版设计模式

  1. registerListeners

为ApplicationContext准备监听器

  1. finishBeanFactoryInitialization

初始化单例Bean 执行Bean后处理器扩展

  1. finishRefresh

准备生命周期管理器,发布ContextRefreshed事件

功能分类

  • 1 为准备环境
  • 2 3 4 5 6 为准备 BeanFactory
  • 7 8 9 10 12 为准备 ApplicationContext
  • 11 为初始化 BeanFactory 中非延迟单例 bean
1. prepareRefresh
  • 这一步创建和准备了 Environment 对象,它作为 ApplicationContext 的一个成员变量
  • Environment 对象的作用之一是为后续 @Value,值注入时提供键值
  • Environment 分成三个主要部分
  • systemProperties - 保存 java 环境键值
  • systemEnvironment - 保存系统环境键值
  • 自定义 PropertySource - 保存自定义键值,例如来自于 *.properties 文件的键值

image.png

2. obtainFreshBeanFactory
  • 这一步获取(或创建) BeanFactory,它也是作为 ApplicationContext 的一个成员变量
  • BeanFactory 的作用是负责 bean 的创建、依赖注入和初始化,bean 的各项特征由 BeanDefinition 定义
  • BeanDefinition 作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等
  • BeanDefinition 的来源有多种多样,可以是通过 xml 获得、配置类获得、组件扫描获得,也可以是编程添加
  • 所有的 BeanDefinition 会存入 BeanFactory 中的 beanDefinitionMap 集合

image.png

3. prepareBeanFactory
  • 这一步会进一步完善 BeanFactory,为它的各项成员变量赋值
  • beanExpressionResolver 用来解析 SpEL,常见实现为 StandardBeanExpressionResolver(实现类)
  • propertyEditorRegistrars 会注册类型转换器
  • 它在这里使用了 ResourceEditorRegistrar 实现类
  • 并应用 ApplicationContext 提供的 Environment 完成 ${ } 解析
  • registerResolvableDependency 来注册 beanFactory 以及 ApplicationContext,让它们也能用于依赖注入
  • beanPostProcessors 是 bean 后处理器集合,会工作在 bean 的生命周期各个阶段,此处会添加两个:
  • ApplicationContextAwareProcessor 用来解析 Aware 接口
  • ApplicationListenerDetector 用来识别容器中 ApplicationListener 类型的 bean

image.png

4. postProcessBeanFactory
  • 这一步是空实现,留给子类扩展。
  • 一般 Web 环境的 ApplicationContext 都要利用它注册新的 Scope,完善 Web 下的 BeanFactory
  • 这里体现的是模板方法设计模式
5. invokeBeanFactoryPostProcessors
  • 这一步会调用 beanFactory 后处理器
  • beanFactory 后处理器,充当 beanFactory 的扩展点,可以用来补充或修改 BeanDefinition

为图中beanDefinitionMap中浅蓝色表示

  • 常见的 beanFactory 后处理器有
  • ConfigurationClassPostProcessor – 解析 @Configuration、@Bean、@Import、@PropertySource 等
  • PropertySourcesPlaceHolderConfigurer – 替换 BeanDefinition 中的 ${ }
  • MapperScannerConfigurer – 补充 Mapper 接口对应的 BeanDefinition

image.png

6. registerBeanPostProcessors
  • 这一步是继续从 beanFactory 中找出 bean 后处理器,添加至 beanPostProcessors 集合中

为图中beanPostProcessors绿色中表示

  • bean 后处理器,充当 bean 的扩展点,可以工作在 bean 的实例化、依赖注入、初始化阶段,常见的有:
  • AutowiredAnnotationBeanPostProcessor 功能有:解析 @Autowired,@Value 注解
  • CommonAnnotationBeanPostProcessor 功能有:解析 @Resource,@PostConstruct,@PreDestroy
  • AnnotationAwareAspectJAutoProxyCreator 功能有:为符合切点的目标 bean 自动创建代理

image.png

7. initMessageSource
  • 这一步是为 ApplicationContext 添加 messageSource 成员,实现国际化功能
  • 去 beanFactory 内找名为 messageSource 的 bean,如果没有,则提供空的 MessageSource 实现

image.png

8. initApplicationContextEventMulticaster
  • 这一步为 ApplicationContext 添加事件广播器成员,即 applicationContextEventMulticaster
  • 它的作用是发布事件给监听器
  • 去 beanFactory 找名为 applicationEventMulticaster 的 bean 作为事件广播器,若没有,会创建默认的事件广播器
  • 之后就可以调用 ApplicationContext.publishEvent(事件对象) 来发布事件

image.png

9. onRefresh
  • 这一步是空实现,留给子类扩展
  • SpringBoot 中的子类在这里准备了 WebServer,即内嵌 web 容器

即onrefresh实现tomcat容器创建

  • 体现的是模板方法设计模式
10. registerListeners
  • 这一步会从多种途径找到事件监听器,并添加至 applicationEventMulticaster
  • 事件监听器顾名思义,用来接收事件广播器发布的事件,有如下来源
  • 事先编程添加的
  • 来自容器中的 bean
  • 来自于 @EventListener 的解析
  • 要实现事件监听器,只需要实现 ApplicationListener 接口,重写其中 onApplicationEvent(E e) 方法即可

image.png

11. finishBeanFactoryInitialization
  • 这一步会将 beanFactory 的成员补充完毕,并初始化所有非延迟单例 bean
  • conversionService 也是一套转换机制,作为对 PropertyEditor 的补充
  • embeddedValueResolvers 即内嵌值解析器,用来解析 @Value 中的 ${ },借用的是 Environment 的功能
  • singletonObjects 即单例池,缓存所有单例对象
  • 对象的创建都分三个阶段,每一阶段都有不同的 bean 后处理器参与进来,扩展功能

image.png

12. finishRefresh
  • 这一步会为 ApplicationContext 添加 lifecycleProcessor 成员,用来控制容器内需要生命周期管理的 bean
  • 如果容器中有名称为 lifecycleProcessor 的 bean 就用它,否则创建默认的生命周期管理器
  • 准备好生命周期管理器,就可以实现
  • 调用 context 的 start,即可触发所有实现 LifeCycle 接口 bean 的 start
  • 调用 context 的 stop,即可触发所有实现 LifeCycle 接口 bean 的 stop

即所有BeanFactory中所有方法均会实现LifeCycle生命周期函数

  • 发布 ContextRefreshed 事件,整个 refresh 执行完成

image.png

4-2.Spring Bean生命周期

要求
  • 掌握 Spring bean 的生命周期
bean 生命周期 概述

bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:

  1. 处理名称,检查缓存
  2. 处理父子容器
  3. 处理 dependsOn
  4. 选择 scope 策略
  5. 创建 bean
  6. 类型转换处理
  7. 销毁 bean

划分的阶段和名称并不重要,重要的是理解整个过程中做了哪些事情

1. 处理名称,检查缓存
  • 这一步会处理别名,将别名解析为实际名称
  • 对 FactoryBean 也会特殊处理,如果以 & 开头,表示要获取 FactoryBean 本身,否则表示要获取其产品
  • 这里针对单例对象会检查一级、二级、三级缓存
  • singletonFactories 三级缓存,存放单例工厂对象
  • earlySingletonObjects 二级缓存,存放单例工厂的产品对象
  • 如果发生循环依赖,产品是代理;无循环依赖,产品是原始对象
  • singletonObjects 一级缓存,存放单例成品对象
2. 处理父子容器
  • 如果当前容器根据名字找不到这个 bean,此时若父容器存在,则执行父容器的 getBean 流程

优先寻找子容器是否存在当前需要寻找的bean,若子容器不存在则查找父容器

  • 父子容器的 bean 名称可以重复
3. 处理 dependsOn
  • 如果当前 bean 有通过 dependsOn 指定了非显式依赖的 bean,这一步会提前创建这些 dependsOn 的 bean
  • 所谓非显式依赖,就是指两个 bean 之间不存在直接依赖关系,但需要控制它们的创建先后顺序
4. 选择 scope 策略
  • 对于 singleton scope,首先到单例池去获取 bean,如果有则直接返回,没有再进入创建流程
  • 对于 prototype scope,每次都会进入创建流程(懒惰式创建)==> 多例模式
  • 对于自定义 scope,例如 request,首先到 request 域获取 bean,如果有则直接返回,没有再进入创建流程
5.1 创建 bean - 创建 bean 实例
要点 总结
有自定义 TargetSource 的情况 由 AnnotationAwareAspectJAutoProxyCreator 创建代理返回
Supplier 方式创建 bean 实例 为 Spring 5.0 新增功能,方便编程方式创建 bean 实例
FactoryMethod 方式 创建 bean 实例 ① 分成静态工厂与实例工厂;② 工厂方法若有参数,需要对工厂方法参数进行解析,利用 resolveDependency;③ 如果有多个工厂方法候选者,还要进一步按权重筛选
AutowiredAnnotationBeanPostProcessor ① 优先选择带 @Autowired 注解的构造;② 若有唯一的带参构造,也会入选
mbd.getPreferredConstructors 选择所有公共构造,这些构造之间按权重筛选
采用默认构造 如果上面的后处理器和 BeanDefiniation 都没找到构造,采用默认构造,即使是私有的(通过反射,将方法设置为可访问并进行默认构造)
5.2 创建 bean - 依赖注入
要点 总结
AutowiredAnnotationBeanPostProcessor 识别 @Autowired 及 @Value 标注的成员,封装为 InjectionMetadata 进行依赖注入
CommonAnnotationBeanPostProcessor 识别 @Resource 标注的成员,封装为 InjectionMetadata 进行依赖注入
resolveDependency 用来查找要装配的值,可以识别:① Optional;② ObjectFactory 及 ObjectProvider;③ @Lazy 注解;④ @Value 注解(${ }, #{ }, 类型转换);⑤ 集合类型(Collection,Map,数组等);⑥ 泛型和 @Qualifier(用来区分类型歧义);⑦ primary 及名字匹配(用来区分类型歧义)
AUTOWIRE_BY_NAME 根据成员名字找 bean 对象,修改 mbd 的 propertyValues,不会考虑简单类型的成员
AUTOWIRE_BY_TYPE 根据成员类型执行 resolveDependency 找到依赖注入的值,修改 mbd 的 propertyValues
applyPropertyValues 根据 mbd 的 propertyValues 进行依赖注入(即xml中 `<property name ref value/>`)
5.3 创建 bean - 初始化
要点 总结
内置 Aware 接口的装配 包括 BeanNameAware,BeanFactoryAware 等(执行优先级最高)
扩展 Aware 接口的装配 由 ApplicationContextAwareProcessor 解析,执行时机在 postProcessBeforeInitialization
@PostConstruct 由 CommonAnnotationBeanPostProcessor 解析,执行时机在 postProcessBeforeInitialization(执行优先级次高)
InitializingBean 通过接口回调执行初始化(执行优先级次低)
initMethod 根据 BeanDefinition 得到的初始化方法执行初始化,即 <bean init-method> 或 @Bean(initMethod)(执行优先级最低)
创建 aop 代理 由 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在 postProcessAfterInitialization
5.4 创建 bean - 注册可销毁 bean

在这一步判断并登记可销毁 bean

  • 判断依据
  • 如果实现了 DisposableBean 或 AutoCloseable 接口,则为可销毁 bean
  • 如果自定义了 destroyMethod,则为可销毁 bean
  • 如果采用 @Bean 没有指定 destroyMethod,则采用自动推断方式获取销毁方法名(close,shutdown)
  • 如果有 @PreDestroy 标注的方法
  • 存储位置
  • singleton scope 的可销毁 bean 会存储于 beanFactory 的成员当中
  • 自定义 scope 的可销毁 bean 会存储于对应的域对象当中
  • prototype scope 不会存储,需要自己找到此对象销毁(手动查找手动销毁)
  • 存储时都会封装为 DisposableBeanAdapter 类型对销毁方法的调用进行适配
6. 类型转换处理
  • 如果 getBean 的 requiredType 参数与实际得到的对象类型不同,会尝试进行类型转换
7. 销毁 bean
  • 销毁时机
  • singleton bean 的销毁在 ApplicationContext.close 时,此时会找到所有 DisposableBean 的名字,逐一销毁
  • 自定义 scope bean 的销毁在作用域对象生命周期结束时
  • prototype bean 的销毁可以通过自己手动调用 AutowireCapableBeanFactory.destroyBean 方法执行销毁
  • 同一 bean 中不同形式销毁方法的调用次序
  • 优先后处理器销毁,即 @PreDestroy
  • 其次 DisposableBean 接口销毁
  • 最后 destroyMethod 销毁(包括自定义名称,推断名称,AutoCloseable 接口 多选一)
总结

image.png



目录
相关文章
|
5天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
9天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
15天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
11天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
35 4
|
12天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
53 4
|
10天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
19天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
6天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9
|
9天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
6天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin

热门文章

最新文章