导图社区 面试题 JAVA-Spring-SpringBoot
这是一篇关于JAVA的思维导图,主要内容包括:基础,Java并发,集合,I/O。无论是应对面试还是实际项目开发,扎实掌握这些JAVA - Spring - SpringBoot相关知识都是至关重要的,让我们通过这些面试题进一步检验和提升自己的技术能力。
编辑于2025-05-23 12:41:07JAVA
基础
概念
三大基本特性:封装,继承,多态
多态
就是同一个操作作用于不同的对象,可以不同的解释,产生不同的执行结果
重写和重载的区别?
重载是编译期概念,重写是运行期间概念; 重载:是在编译时根据参数变量的类型判断用哪个方法 重写:是在运行的时候,根据引用变量的实际对象类型引用
JavaEE 和 Java SE
SE:基础Java平台,本地应用程序,适用桌面应用,嵌入式系统等 EE:用于开发Web应用,微服务,分布式系统
反射机制
:就是程序运行的时候获取自身信息;只要给定类的名字,那么就可以通过反射获取类的所以属性和方法; 优点:提升程序灵活性; 缺点:代码可读性差,维护性差,执行性能低,破坏了封装性
原理:

首先,通过编译,将源码编译成字节码; 然后,在运行阶段,需要调用对象或者new对象的时候,会触发类加载器; 最后,通过类加载器,将字节码加载到内存中,转换成运行时的数据结构,及创建一个class对象,成员都是数组形式
实现方式:
利用反射创建对象

1、获取类对象,在进行newInstance() 2、获取他的构造器,通过构造器newInstance()
利用反射获取方法

利用反射获取成员变量

反射机制为什么慢?
1、不能执行Java虚拟机的优化,如 JIT优化 2、使用时需要包装(boxing)成Object[],执行时需要拆包(unboxing) 3、反射调用的时候会从方法数组中遍历查找,耗时 4、除了方法的可见性检查,还有参数也要额外检查
在运行时可以做到:
1、判断任意一个对象所属的类 2、判断任意一个类具有的成员变量和方法 3、任意调用一个对象的方法 4、构造任意一个类的对象
反射使用场景:
1、动态代理
就是通过反射机制实现
2、RPC框架

1、服务注册阶段 2、动态代理生成 3、参数序列化反序列化
3、Spring的IOC和DI
4、JDBC的class.forName 5、BeanUtils中属性值的拷贝 6、ORM框架
反射可以破坏单例模式
动态代理?
一种在运行时动态创建代理对象的技术
浅拷贝和深拷贝
浅拷贝:复制所有对象数值包括引用地址,引用地址指向同一个 深拷贝:基础数值直接复制,引用类型是创建新的对象副本,引用地址不同
泛型
:声明的类型参数,在使用时用具体的类型替换 Java泛型只在编译期,jvm不会感知到
好处: 方便,可以提高代码复用性; 安全,在编译时会做类型检查;
类型擦除:实现泛型的一种方式,也就是通过语法糖的形式
编译后的字节码文件中,会把泛型擦除掉
将泛型Java代码转换为普通代码
泛型 上下界限定符 extends 和 super
<? extends T> :类型的上界,表示参数化类型可能是 T 或是 T的子类
语法糖
switch支持String,枚举

泛型

枚举

Java程序的生命周期

编译阶段
将源码编译成字节码
加载阶段
通过类加载器,将字节码文件加载到内存中 ; 转换成运行时的数据结构:创建一个class对象,包含成员变量,构造器,成员方法
运行阶段
当需要new对象,或者调用,就会触发类加载器
JIT优化?
代码 编译成 字节码,jvm内置的 解释器 翻译成机器码然后执行; 解释器的执行方式是一边翻译一边执行效率低,引入JIT技术;
当jvm发现某个方法或代码块运行的时候频繁执行,则默认为‘热点代码’; JIT会把部分热点代码翻译成机器码,并进行优化,然后将翻译后的机器码缓存,以备使用
热点检测:
1、基于采样的方式探测(Sample Based Hot Spot Detection) :周期性检测各个线程的栈顶,发现某个方法经常出现在栈顶,就认为是热点方法。好处就是简单,缺点就是无法精确确认一个方法的热度。容易受线程阻塞或别的原因干扰热点探测。
2、基于计数器的热点探测(Counter Based Hot Spot Detection)。采用这种方法的虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,某个方法超过阀值就认为是热点方法,触发JIT编译。
优化部分:逃逸分析、 锁消除、 锁膨胀、 方法内联、 空值检查消除、 类型检测消除、 公共子表达式消除
异常:
1、ERROR
OutOfMemoryError :jvm没有足够内存存放对象 StackOverflowError :方法调用栈深度超过jvm允许的最大深度,如递归方法没有正确终止
2、Exception(异常)
受检查异常(Checked Exception)
IOException
SQLException
非受检查异常(Unchecked Exception) 也叫: 运行时异常
NullPointerException
ArrayIndexOutOfBoundsException
数据类型
基础数据类型
布尔:boolean, 整型:byte,short,int,long 字符:char 浮点:float,double
String
512M
String 是不可变的 Stringbuilder 和 StringBuffer 是可变的 StringBuffer 是线程安全的 StringBuilder 是线程不安全
boolean 和 Boolean 的区别?
boolean isSuccess; boolean success; Boolean isSuccess; Boolean success;
使用第四种,避免二义性:boolean默认false,Boolean默认为null,当拿到false就不清楚是接口出了问题还是返回false;
阿里巴巴规范中也讲到,任何布尔类型都不要加is 比如定义为基本数据类型 boolean isSuccess;的属性,它的方法也是 isSuccess(), RPC框架在反向解析的时候, “ 以为” 对应的属性名称是 success,导致属性获取不到,进而抛出异常。
euqals() 和==的区别?重写equals()需要注意什么?

区别:
==:比较运算符,对象比较的是引用地址 equals():是方法,值比较
equals()比较: Integer:先比较地址,再比较数据类型,然后比较值 String:先比较地址,再比较数据类型,然后比较字符串长度,最后计较每个字符是否一样
重写equals()需要注意什么?
注意重写hashcode()方法
如果只重写equals方法,不重写hashCode方法。就有可能导致a.equals(b)这个表达式成立,但是hashCode却不同; 在存放到散列集合(hashmap,hashtable等),会使用他的hashcode存放,就导致hashcode不同,但是对象相同的情况,出现不可预知的问题风险
单位:
8 bit = 1byte ; 1024byte = 1KB ; 1024KB = 1M ; 1023M = 1GB ;
为什么能跨平台?
1、Java编译成统一的字节码 2、每个平台上都会有jvm
拦截器和过滤器
过滤器:实现Filter接口,基于Servlet,适用于所有web请求 拦截器:实现HandlerInterceptor,基于springMVC,只能拦截controller
Java并发
创建线程的4个方式
继承Thread
实现Runnable接口
通过Callable和FutureTask创建线程
有返回值
实现线程任务的状态,是否已经执行完成
通过线程池
参数

1、核心线程数:corePoolSize
:新任务优先核心线程执行,线程一直存活运行
2、最大线程数量:maximumPoolSize
:若核心线程都在执行任务,且任务队列已满,则创建新线程,直到线程数量到达最大线程数
3、任务队列
1、ArrayBlockingQueue 基于数组 有界阻塞队列
2、LinkBlockingQueue:基于链表 可选有界阻塞队列
3、SynchronousQueue:不存储元素的阻塞队列
不存储任务,当线程数超过核心线程数,则尝试创建新的线程 及最大线程数
4、非核心线程空余时间:keepAliveTime
当线程数量超过核心线程,且空闲时间超过改时间,则被销毁,直到恢复到核心线程数
5、拒绝策略:handle
:当线程池达到最大容量,且队列也满时的拒绝策略
1、AbortPolicy:默认拒绝策略,抛出reject异常;(经常使用 通常会提升执行速度,或者增加队列长度) 2、DiscardPolicy:丢弃新任务,不抛异常;适合日志记录,统计不重要的任务 3、DiscardOldestPolicy:丢弃最早的任务,尝试提交新任务;适合实时性较高的任务;(适用实时性较高的任务场景) 4、CallerRunsPolicy:将任务回退给调用线程,不抛异常;适用可承受一定压力,拥有缓冲机制 5、自定义处理
6、空余时间单位
7、创建线程工厂

自定义线程池中的线程创建方式,比如定义线程的名称
创建?
阿里巴巴开发手册推荐的:ThreadPoolExecutor创建,至少5个参数,最多7个参数
常用:Springframework内置线程池: ThreadPoolTaskExecutor 问:和jdk内置的线程池创建方式有什么区别?

参数:核心线程数,最大线程数,任务队列,拒绝策略; 拒绝策略:1、保存拒绝的任务 2、通知人员手动处理
线程数设置多少合适?
影响因素: CPU核数 --多核处理器,一个CPU处理一个线程最好 --超线程技术,2倍的CPU核数 JVM系统资源 --内存限制,每个线程占用的内存空间,避免内存溢出 --操作系统限制,过会导致性能下降 应用类型(N是CPU总核数) --CPU密集型,密集型任务,线程数 N + 1 --I/O密集型,任务涉及大量等待或阻塞,配置更多线程,2N +1 其他 --RT要求,可以增加线程数处理延迟 --任务特性,长时间段时间任务,同步异步任务,都需要考虑不同的线程数
公式:CPU核心数 * 目标CPU利用率 * (1 + 等待时间/计算时间)
不建议套用公式,根据实际业务,压测等在不断调整
总结: 通过CPU核数,结合服务的业务处理形式
队列长度:
CPU密集型:100-500 IO密集型:1000-5000
线程执行过程?

向线程池中添加一个任务,得到当前线程数 1、如果小于核心线程数,则创建新线程执行 2、若大于核心线程,则放到任务队列 3、若核心线程已满,任务队列已满,则创建新的线程执行 4、若线程数大于最大线程数,则根据拒绝策略执行(直接抛异常,直接丢弃新任务记录日志,丢弃最早任务,任务退回给调用的线程)
非核心线程的回收?
实际线程不区分 核心线程 和 非核心线程,超时后随机回收线程
线程池状态?
1、初始-New:创建新线程对象,没有执行start 2、运行-Runnable:就绪和运行中 统称运行状态 就绪-Ready:调用start方法,等待分配CPU时间片 运行中-Running:就绪的线程获取到时间片,开始执行程序 3、阻塞-Blocked:线程阻塞于锁,关于锁 4、等待-waiting:该状态的线程需要等待其他线程处理特定动作 5、超时等待-timed_waiting:指定时间后自行返回,不同于等待 6、终止-Terminated:线程池完全终止 ShutDown:调用shutdown(),线程池停止增加新任务,继续执行队列任务 Stop:调用了`shutdownNow(),线程池停止增加新任务,停止处理队列中的任务,并中断执行进行的任务 Tidying:所有任务终止,工作线程数为0,准备执行终止 Terminated:线程池完全终止
线程池的预热?
通过 prestartAllCoreThreads()和 prestartCoreThread()两个方法实现线程预热和指定线程预热
多线程优化:
1、线程池参数配置:核心线程数,最大线程数
2、使用无锁的数据结构
3、线程安全的队列,
ForkJoinPool
解决递归分解的任务
每个线程都有自己的任务队列,当前队列任务做完,会窃取其他线程队列任务,提高任务执行效率
并发编辑工具?
1、基础并发工具
1、线程池框架
ExecutorService
:线程池基础接口
ThreadPoolExecutor
:标准线程池实现
ScheduledExecutorService
:定时任务线程池
ForkJoinPool
:分治任务专用线程池
2、同步工具类
例:三个线程分别输出10次 A,B,C
依次执行?
方法一:Semaphore 控制
方法二:volatile
方法三:join
交替执行?
Semaphore
三个信号 控制获取和释放
同时执行?
CountDownLatch
CyclicBarrier
Semaphore
控制对共享资源的访问数量;先获取许可,再释放
共享信号,拿到信号就能跑;
acquire(),release()
CountDownLatch
信号枪 3 2 1 跑
不可重复使用
若需要重复使用,需要重写CountDownLatch,增加reset方法(RocketMQ源码有重写,提供的小工具)
CyclicBarrier
线程间的同步等待, 所有线程完成任务后,收集任务结果
水坝蓄水,1 2 3 跑
Phaser
AQS 了解吗?
Abstract Queued Synchronizer 抽象队列同步器,用于构建锁和同步器 一个框架
实现原理:
通过FIFO队列管理获取资源失败的线程; volatile 修饰的int 表示共享资源状态; CAS实现原子的更新;
模式:
独占模式:同一时间只有一个线程拿资源 ReentrantLock 共享模式:多个线程可以获取资源 Semaphore
3、并发集合
ConcurrentHashMap
:线程安全HashMap
CopyOnWriteArrayList
:写时复制List
适用场景:读多写少的并发场景
ConcurrentSkipListMap
:通过跳表机制实现
有序,线程安全,subMap获取某段数据
BlockingQueue:阻塞队列接口
:数组实现的有界队列
ArrayBlockingQueue
:链表实现的可选有界队列
LinkedBlockingQueue
:带优先级的无界队列
PriorityBlockingQueue
:不存储元素的直接传递队列
SynchronousQueue
2、原子类操作
1、基础原子类
AtomicInteger
AtomicLong
AtomicBoolean
2、引用原子类
1. AtomicReference
2. AtomicStampedReference
(带版本号,解决ABA问题)
3、数组原子类
AtomicIntegerArray
AtomicLongArray
4、字段更新器
AtomicIntegerFieldUpdater
AtomicReferenceFieldUpdater
问题?
AtomicInteger 和 volatile 的区别?
保证变量的可见性和有序性; AtomicInteger保证原子性
AtomicInteger
他的 CAS操作是通过硬件执行实现,不使用锁的情况下实现原子操作
其他方法:incrementAndGet(),getAndIncrement()
前者返回的是 递增后的值,后者返回的是递增前的值
Java中如何保证线程安全 ?
线程安全:指的是函数在并发调用时,能够正确处理多个线程之间共享变量的问题,使程序正确运行
线程安全集合?
List接口:CopyOnWriteArrayList 基于ReentrantLock实现线程安全
set接口:ConcurrentSkipListSet 基于CopyOnWriteArrayList集合实现
queue队列:那些阻塞队列
map接口:ConcurrentHashMap 通过synchronized + CAS实现
方案:
1、单线程
Redis
2、互斥锁
加锁进行排队;
无论是synchronized和reentronied这种单机锁 还是Redis实现的分布式锁, 还是数据库的乐观锁、悲观锁; 思想都是将并发请求进行排队执行
3、读写分离
COW机制,即写时复制,如CopyOnWritesArrayList,add新元素时,先将原数组拷贝一份出来,然后在新数组做写操作,写完后,再将原数组指引到新数组
什么是COW,如何保证线程安全?
Copy On Write,一开始大家都是共享同一个内容,当某人想要修改内容的时候,才会把内容copy出去成新的修改,这是一种延迟懒惰策略; CopyOnWriteArrayList的整个add操作都是在锁的保护下进行,及add操作是线程安全的; CopyOnWrite并发容器 用于 读多写少 的并发场景;
相比较ArrayList,它具有的特性: 1、支持高效率并发 且 线程安全 2、因为需要复制整个数组,所以操作方法 add()、set()、remove()等开销大 3、迭代器支持nextHash()、next()等不可变操作,不支持remove()可变操作 4、迭代器遍历速度很快,且不会与其它线程发生冲突。依赖不变的数组快照
4、原子操作
及不可中断操作,要么全部成功,要么全部失败; 多线程中可以使用原子操作来实现共享资源的安全访问;如AtomicInteger 其底层依赖操作系统的CAS指令,思想也就是 Compare And Swap
什么是CAS,存在什么问题?
5、不可变模式
只有读操作,永远线程安全;
应用?
Java中String就是不可变模式的一种体现
6、数据不共享
ThreadLocal:

是什么?
java.lang下面的一个类,用于解决多线程并发问题; 通过为每一个线程创建一份共享变量的副本 来保证线程之间变量的访问和修改互不干扰; 四个方法:initialValue(),get(),set(),remove(); ThreadLocalMap : key是ThreadLocal对象,v就是存放的变量值;Thread对象访问该类读取变量数据
参数 如何传递? 线程池 如何传递
为什么会导致内存泄露?
主要在 ThreadLocalMap
1、栈上的ThreadLocal ref不在引用,但是还有一条引用链在,导致无法被回收 2、Thread一直被使用,这条引用链一直存在,导致ThreadLocalMap无法被回收
解决:
1、ThreadLocalMap 使用了 弱引用,及 栈上ThreadLocal ref这条引用不在使用 2、手动清理ThreadLocal :在ThreadLocal用完后,手动调用remove,下次GC的时候自动回收
虚拟线程 避免使用 ThreadLocal
线程问题
2、什么是线程同步?线程同步方式?
:让多个线程之间按照顺序访问同一个资源,避免并发
synchronized
1、基础线程同步方式 2、修饰代码块或方法 3、保证同一时间只有一个线程访问
底层实现:基于对象实现
ReentrantLock

代码中使用更灵活,可以在任何地方加锁解锁
高级特性:灵活的锁控制能力 1、可中断锁 lock.lockInterruptibly(); 2、创建公平锁- private final ReentrantLock lock = new ReentrantLock(true); 3、尝试获取锁,可设置超时时间 lock.tryLock(1, java.util.concurrent.TimeUnit.SECONDS)

公平锁 和 非公平锁的区别?
公平锁可以经可能保证顺序获取锁,非公平锁则不能保证顺序
两者区别?
1、前者可以修饰实例方法、静态方法、代码块,后者需要手动创建锁的实例,可以灵活控制锁的粒度 2、前者线程进入的时候自动获取锁结束自动释放锁,后者需要手动lock()unlock()获取释放锁 3、前者是非公平锁,后者可以支持公平锁和非公平锁 4、前者不可中断,后者可以 5、前者操作简单,后者支持更多高级特性
并发编程多个线程
CompletableFuture:异步编排,任务组合

底层实现:
completion链式异步处理、事件驱动、ForkJoinPool线程池、以及CountDownLatch控制计算状态、通过CompletionException捕获异常
1、内部通过线程池实现异步计算 1、内部采用链式结构来处理异步执行结果 2、采用事件驱动的机制实现异步计算的完成事件
ExecutorService
Future
线程同步和线程协作是怎么理解?
线程同步:解决的是共享资源访问的互斥问题 线程协作:解决的是线程执行顺序协调问题
什么是多线程的上下文切换 ?
指CPU从一个线程跳转到另一个线程时,需要保存当前线程的上下文状态,恢复另一个上下文状态,以便于下次恢复时该线程正确执行;
上下文切换开销 比直接用 单线程大; 每次切换需要保存大量上下文信息; 频繁的切换 降低系统运行效率
Thread.sleep(0);有什么要用?
:让当前线程释放一下CPU时间片,然后重新开始争抢
锁
锁的分类
乐观锁 悲观锁
悲观锁 先锁再用 乐观锁 【自旋锁】 乐观锁的CAS机制,即比较并且替换机制 
前者 先操作,冲突了在CAS,后者 先加锁在操作
公平锁 非公平锁
new ReentrantLock(true) 公平锁, 设置为false,则为非公平锁 公平锁输出结果:分别5次输出 t1,t2,t3 非公平锁输出结果:分别输出5次t1,5次t2,5次t3   
公平锁:排队等待先来后到的顺序进入, 非公平锁:不按照次序
共享锁 排他锁
写锁具有排他性,读锁具有共享特性
可重入锁 不可重入锁
同一个线程持有的锁 ,可以重复持有
锁优化
1、较少锁持有时间,同步代码块 替换 同步方法
2、减少锁粒度
要在并发场景中使用Map的时候,记得使用ConcurrentHashMap来代替HashTable和HashMap。
3、锁消除
编译器会分析所对象是否只有一个线程访问,此时会取消这块代码的同步
锁消除:人不多的时候,可以不用取号,直接去处理
4、锁粗化
多次的所操作合并处理
5、锁分离
将读写操作分离处理
深入synchronized
是什么?有什么用?怎么实现的? 锁的是什么?
他是Java中的一个重要的关键字; 主要用来加锁,可修饰方法和代码块; 最终锁的都是对象!Java中一切皆为对象
同步方法(修饰方法)

实现:是通过一个ACC_SYNCHRONIZED标志是否设置,若有则获取监视器锁,开始执行方法,方法结束释放监视器锁; 期间其他线程访问由于无法获取监视器锁被拒绝; 若方法发生异常,切没有捕获,则会在方法外面释放监视器锁;
sychronized普通方法,锁的是具体调用的实例对象; sychronized静态方法,锁的是这个方法所属的类对象;
同步代码块

实现:通过monitorenter 和 monitorexit,用于加锁和释放锁,每次加锁会被计数,计数器为0时,释放锁
synchronized(this)锁的是this这个实例对象; sychronized(Xxx.class)锁的是这个类对象;
如何保证原子性,可见性,有序性?
原子性:通过monitorenter和monitorexit实现
有序性:Java程序天然有序性,因为同步代码块同一时间只能有一个线程来执行,而在一个线程内可以保证其有序性
可见性:同步关键字锁住的对象,其值是具有可见性
synchronized底层锁升级的四中状态: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 随着并发量的增加,锁的重量也会增加,锁越重,性能越低,但是越安全
1、最初是无锁状态 2、当第一个synchronized块线程使用,升级为偏向锁,偏向第一个线程,也可看作可重入锁,偏向同一个线程 3、当串行另一个进程尝试获取已被偏向的锁时,则升级为轻量级锁,底层是一个自旋锁实现,Java实现 4、当并发状态,则升级为重量级锁,由操作系统底层monitor实现,不会像自旋锁占用cpu资源
重量级锁 虽然开销大,但是安全
锁优化
自旋锁
自旋锁和阻塞锁的区别在于到底要不要放弃处理器执行时间; 自旋 可以时刻检查共享资源是否可以访问;阻塞锁则需要等待被唤醒;
阻塞锁:银行取号,依次排队到柜台处理 自旋锁:自动取款机,有人就可以直接处理,没人一直等待
锁消除
编译器会分析所对象是否只有一个线程访问,此时会取消这块代码的同步
锁消除:人不多的时候,可以不用取号,直接去处理
锁粗化
加锁的时候,提倡尽量减小锁粒度,但是避免重复锁同一个对象
锁粗化:同一个人需要办理几个不同的业务,只要取一个号就行
Volatile (没有加锁)
用于修饰变量,相当于轻量级锁,确保变量在多个线程之间的可见性;
线程间可见性,避免指令重排
不能保证原子性,因为没有加锁,只能保证线程之间的可见性
适用于:一个线程写,其他线程读
优雅停机
工具:
asyncTool:解决任意的线程 并行,串行,阻塞,依赖,回调的框架
集合

Collection
List
ArrayList,LinkedList,Vector有什么区别?
ArrayList是一个可变大小的数组; LinkedList是一个双向链表;添加删除性能更好; Vector属于强同步类,和ArrayList 注:线程安全的情况下,ArrayList更好; 每次请求更大空间的时候,arrayList是0.5倍,vector是双倍
读多写少,随机访问频繁 →ArrayList 写多读少,频繁结构调整 →LinkedList 不确定时,优先选择 ArrayList (大多数场景性能更优)
ArrayList是如何扩容?
1、检查数组是否查过容量,超过则扩容,初始默认10 2、设置新容量是原来的1.5倍,最大不差过Integer.MAX_VALUE - 8(2^31-9) 3、申请的1.5倍数组,将老数组复制到新数组中
vector
Stack
push:栈顶添加元素 pop:移除栈顶元素 peek:读出栈顶元素,不移除
后进先出
结构:前四种是单一元素,Map是KV形式 继承:List,Set,Queue继承了Collection子接口,Collection继承了Iterrable,及可遍历 功能:List代表一个容器,可先进先出,可先进后出;Set无序,同时也是去重列表;Map是映射 实现:List可链表可数组,链表增删快,数组查询快;queue可以分为双端队列或者优先队列等等;Map可分为普通hashMap 和 TreeMap;
ArrayList 和 vector的区别
前者是线程不安全,后者线程安全
Set
HashSet
add数字的化,结果是无序的吗?
Queue
BlockingQueue
LinkedBlockingQueue
链表结构,方便插入删除; 高并发性能
LinkedBlockingDeque LinkedBlockingQueue
offer:队列尾部添加新元素,添加成功返回true,队列满添加失败返回false add:队列尾部添加新元素,若队列满了则抛异常 poll:移除头部元素并返回,队列为空返回null remove:移除头部元素并返回,队列为空抛异常 peek:获取头部元素,队列为空返回null element:获取头部元素,队列为空抛异常
先进先出
Deque 双向阻塞队列,两端可以添加删除元素
peek:获取头部元素,队列为空返回null pop:
Map
HashMap、HashTable、TreeMap、CurrentHashMap

线程安全:
HashMap非线程安全 HashTable方法是同步的,线程安全
多线程情况下使用 CurrentHashMap
继承关系:
Hashtable是基于陈旧的Dictionary类继承来的。
HashMap继承的抽象类AbstractMap实现了Map接口;
ConcurrentHashMap同样继承了抽象类AbstractMap,并且实现了ConcurrentMap接口
允不允许null:
HashMap允许,其他不允许
默认初始容量和扩容机制:
HashTable默认11,其他默认16,超0.75进行扩容
遍历方式的内部实现不同:
CurrentHashMap 如何保证线程安全?
如何:通过分段锁实现,每段都通过锁实现,当一个线程访问某段时,其他线程会访问其他段
源码实现:
JDK1.8后使用 CAS + Synchronized 实现: 添加元素时,某段为空,则通过CAS添加新节点; 如果段 不为空,使用Synchronized锁住当前段,再次尝试put;
CAS:Compare And Swap 先比较再替换:是一种乐观锁机制 1、内置位置V,预期原值A,新值B,先比较A和v去出来的值是否相等,相等泽替换,否则不变 存在的问题:ABA问题;忙等待(并发自旋导致的等待); 场景:原子类(AtomicInteger、AtomicLong、AtomicBoolean),并发容器(ConcurrentHashMap),同步工具类(AQS)
高并发下的性能问题?
因为它采用的是分段锁
高并发下的扩容死锁问题?
HashMap 深入理解底层原理?如何扩容?
底层实现?
数据结构
jdk1.7:数组 + 链表 jdk1.8:数组 + 链表 + 红黑树
JDK1.8之前用的就是 链地址法
之后引入 红黑树,解决因hash冲突导致某个链表长度过长,链表过长后get和set复杂度接近O(N)
数组 + 链表
数组 寻址容易,插入删除困难 链表 寻址困难,插入和删除容易
key:通过 hash(key)%16取模位运算 得到 哈希桶位置
jdk7:entry对象
jdk8:new node()对象
node对象存储的值: hash值,key数据,value,next
hash冲突,则通过next属性,指向下一个元素; 并在桶上生成一个链表,元素越多,链表越深,影响查询效率
链表长度 > 8 ,数组长度 > 64为红黑树 链表长度 > 8 ,数组长度 < 64则扩容
HashMap如何扩容?
超过扩容阈值threshold,则初始化桶数组原来的两倍,需要考虑三个情况: 1、桶节点还没有形成链表,则rehash到其他桶中 2、桶已经形成链表,则将链表重新链接 3、桶已经形成红黑树,但链表中元素小于6,则取消树化操作
初始容量:16
初始扩容阈值=12 = threshold = capecity * loadFactor(负载因子=0.75) = 16*0.75
数组达到64 且 链表>8,转 红黑树 红黑树的数据 低到只有6,转 链表
扩容后的位置如何定位?
jdk1.7:哈希值重新取模,rehash
jsk1.8:扩容后 16*2,hash & 老数组长度 >=16 高位元素:原下标 + 扩容的大小 <16 低位元素:保留原来下标位置
为什么用红黑树?
自平衡的二叉查找树,采用的二分法
需要指定长度吗?默认16
何时需要指定长度?

1、预先知道长度的情况? 2、性能敏感场景:高并发下,减少扩容带来的性能压力 3、内存敏感:明确知道存放多少数据
场景使用?
电商购物车;用户会话存储;

遍历方式?
1、entrySet () 2、keySet() 3、values() 4、forEach() 5、迭代器Iterator 6、Stream流,将entrySet转为Stream流
HashTable 和 CurrentHashMap 的不同?
都是线程安全,有什么不同?
前者锁的是整张hash表,后者采用分段锁实现
前者不允许null,后者允许为空
前者是强一致性,后者是弱一致性
强一致性:在迭代的时候不能改变集合结构,不能修改数据 弱一致性可以修改,但不保证数据准确性
HashMap 和 TreeMap 的区别 ?
无序和有序,前者只能有一个null,后者不能为null
I/O
JAVA
java 21
虚拟线程
主要是提高吞吐量
不能跨越多个处理器,或者说跨越多个核心去处理
通过少数的线程去执行代码,轻松创建和销毁,不需要池化
1、不建议使用 ThreadLocal,可以通过Scoped Values替代方案
编码
新增顺序集合:SequencedCollection

switch 增强:

参数类型匹配处理,可以传递参数类型,根据类型处理
记录类 record:
不是以往的class,而是record
ZGC
其他
取消32位的jdk
增加代码预览功能
java 24
编码
其他
弃用32位的电脑系统
Spring/
概述
JavaEE API 的封装
Spring框架对JavaEE开发中非常难用的一些API(如JDBC、JavaMail、远程调用等)进行了封装,降低了这些API的应用难度。
Spring是什么?
是一个开源的应用框架,简化应用开发,包含IOC,DI,AOP等核心功能方便开发
SpringBoot是基于Spring框架,简化了开发流程,起核心功能包括 自动配置,内置Tomcat ,起步依赖简化等
IOC--控制反转
IOC是Spring框架的核心特性之一,它实现了对象之间的解耦。通过IOC,对象的创建和依赖关系的管理都由Spring容器来完成,开发者不再需要手动创建对象并管理它们之间的依赖关系。
一句话:不需要直接new对象,被动接受由容器注入的对象
底层实现?

1、从配置元数据中获取要DI的业务POJO(这里的配置元数据包括xml,注解,configuration类等)
2、 将业务POJO形成BeanDefinition注入到Spring Container中
3.、使用方通过ApplicationContext从Spring Container直接获取即可。

创建bean的几种方式
1、@Component,@Service,@Controller,@Repository
1、通用组件声明注解,@Component 可以使用在任何spring组建中 2、标记服务层,Service和Component使用一样 3、标记控制层,通知spring该类作为控制器处理HTTP请求 4、标记数据访问层,及DAO,还提供持久化特定功能,比如异常转换
2、通过@Bean注解
1、在类上面使用注解 @Configuration,内部方法使用@Bean
3、通过xml配置
springboot出现前,使较多
4、使用@Import注解
作用:快速导入一个类或多个类
其他注解
@DubboService
Bean加载顺序?

@DependsOn(“beanA”)

在beanA加载之后再加载当前修是的bean
@Order或Ordered接口

order设置优先级,且只在同一个类型Bean生效,如都是多个@Component,BeanPostProcessor、@Configuration类
实现SmartLifecycle接口

通过getPhase()返回值控制顺序,数值越小,启动越早
利用@PostConstruct和InitializingBean

延迟Bean的初始化
配置文件拆分与@Import顺序

Import(A.class,B.class)按顺序倒入
环境变量或Profile控制

控制环境配置加载
DI --依赖注入方式
@Autowired 字段注入

【不建议使用】
构造器注入

setter注入

使用@Resource和@Inject

使用xml配置文件

构造器自动注入

FactoryBean

FactoryBean 和 BeanFactory
FactoryBean:

1、是一个接口,用于定义工厂Bean,可以产生某种类型对象
BeanFactory:

1、Bean工厂,是整个Spring IOC的一部分,负责管理Bean的创建和生命周期
2、是IOC容器的一个接口
Bean的生命周期
创建
实例化
初始化
注册Destruction回调
使用
销毁
解决循环以来的方式?
三级缓存

一级:存储的是 完全创建好的单例been对象 二级:存储的是 尚未完全创建好的单例been对象 三级:存储的是 单例been的创建工厂
AOP
AOP是Spring框架的另一个重要特性,它允许开发者在不修改源代码的情况下,对程序进行权限拦截、运行监控等功能。AOP通过定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。
实现原理?
主要通过动态的为目标对象创建代理对象
实现动态代理的方式
JDK动态代理

:通过反射;目标类必须实现接口
CGLIB动态代理
 
:通过继承;目标类未实现接口
如何选择代理方式?
JDK:目标类实现了接口,对性能要求不高
CGLIB:目标类没有实现接口,除final以外的接口
SpringBoot2.X以上版本,默认CGLIB
常见名词:切面,切点,连接点
是什么?优点?
对面向对象编程的一种补充;对某一类事件统一处理;
优点: 1、减少重复代码量
使用场景?
1、用户登录和鉴权 2、统一日志记录 3、统一方法执行时间统计 4、统一的返回格式设置 5、声明式事务实现
@Aspect
AOP什么场景下会失效?
1、同一个类的内部方法调用 2、通过 new关键字 手动创建对象调用 3、切入点 配置错误
Spring 事务
事务隔离级别
:1、默认底层数据库的设置隔离级别 2、读未提交 3、读已提交 4、可重复读 5、序列化
如何开启事务?
编程式
 
声明式

事务注解: @Transactional(propagation = Propagation.REQUIRED ,rollbackFor = Exection.class)
参数一:定义传播机制
REQUIRED:如果不存在事务,则开启一个事务,如果存在事务则加入之前的事务,总是只有一个事务在执行 REQUIRES_NEW:每次执行 新开一个事务,如果存在事务,则挂起 SUPPORTS:有事务则加入事务,没事务则普通方法执行 NOT_SUPPORTED:有事务则暂停该事务,没有则普通执行 NEVER:有事务则报异常
参数二:哪些异常触发自动回滚
默认仅对 RuntimeException 和 Error 及其子类触发回滚
设置的回滚异常是对其扩展
IOException
优点
颗粒度问题
作用在方法上
事务失效问题?
失效场景

1、事务内开启异步线程,会导致事务失效,跨线程问题
2、@Transactional注解 用在非public方法上

3、注解的属性设置错误(传播的机制,异常触发的类型)
4、同一个类中的方法被调用

原因:声明式事务是通过AOP实现 动态代理实现,同一个类的方法被调用会绕过代理对象直接调用目标方法
5、异常被catch捕获
spring事务在多线程下生效吗?
不会生效
如何解决?
1、编程式事务
2、分布式事务
方法上加final,事务会失效吗?
事务失效和final无关
加final只会有两种结果: 1、事务不受影响 2、执行报错
Spring事务事件如何使用?
@TransactionalEventListener 处理事务的注解
如何用Spring Event做事件驱动?
场景:假设用户在注册成功后,需要发送一条欢迎短信
 
1、定义一个Event,继承 ApplicationEvent 2、定义监听者,用来执行发送短息操作;方法上加@EventListener(当前类.class) 3、定义发送者,用户注册成功后,通过ApplicationContext.publicEvent()发布出去
默认同步,如果需要异步,在第二步监听者加@Sync
场景优化:@Sync的异步,是没有最大线程数,最好自定义线程池 配合 @Sync(指定配置的线程池)

事件驱动 和 MQ的区别?
1、在使用范围看的化,事件驱动只适合在同一个系统中,MQ支持系统之间的通讯 2、事件驱动时间是不可变更的,MQ的化他可以是消息也可以是命令 3、事件驱动不需要知道消费者是谁,MQ的消费者得知道队列或者topic在哪
Spring中的设计模式
工厂模式:Spring IOC 容器就像一个工厂,创建的时候只需要配置文件及注解
单例模式:spring中的Bean默认都是单例模式,保证对象的复用和线程安全
默认单例,不一定是单例
Spring Scope作用于,通过配置Scope的作用域
模板方法模式:spring的事务管理(执行业务逻辑,有异常则事务回滚,否则提交事务)
代理模式:SpringAOP就是典型的代理模式
应用场景
如何解决 循环依赖?
三级缓存
一级缓存 singletonObjects 也叫 单例池
存储的是 经历完整创建过程的单例bean对象;
存储方式:是一个ConcurrentMap; key是String类型,保存的是beanName;value是Object类型,保存的是创建好的bean对象;
主要作用:保证这些创建好的bean是单例
二级缓存 earlySingletonObjects
存储 尚未完全创建好的单例bean对象
存储结构 和一级缓存一样,但是 存储的对象不一样,村的是半成品对象;
主要作用:保证这些尚未完全创建好的半成品对象是单例
三级缓存 singletonFactories
存储的是 尚未完全创建好的单例bean对象的生成工厂
存储方式:是一个HashMap, key是string型的beanName ,value是ObjectFactory类型是一个接口
过程:
1、先在一级缓存找 beanName 2、没有再从二级缓存中找 beanName 3、没有的话,先锁住二级缓存,分别再从一级二级缓存中找 4、都没有的化,再从三级缓存中找beanName
但依然认为循环依赖是不合理的,默认是关闭循环依赖
开启循环依赖:
1、配置文件中加入 spring.main.allow-circular-reference=true 2、在@Autowired 地方用 @Lazy注解
其他
4、优秀框架的直接支持
Spring框架不排斥各种优秀的开源框架,如Struts、Hibernate、MyBatis等,并提供了对这些框架的直接支持。
SpringBoot
如何让你的Bean在其他Bean之前加载
1、直接依赖某个Bean

2、DependsOn

3、BeanFactoryPostProcessor
相比较提高了几方面效率
1、自动配置
2、内置Web服务器
3、约定大于配置
SpringBoot
基础问题
1、@Controller 和 @RestController的区别?
1、响应的返回结果不同,后者返回json/xml类型,不需要配合@ResponseBody来使用 2、前者就是视图的渲染,后者适用RESTful API
2、@Schedule定时任务,和其他xxl-job,powerjob的区别?
schedule:单机轻量级的定时任务,不能随意修改定时任务,单点故障,不支持分布式,不能可视化
xxl-job:分布式的任务调度平台,可视化,提供web平台管理,可以集群部署,解决单点故障问题
PowerJob:相比较xxl-job,其支持随时启停任务,动态扩缩容,工作流的设置功能更强大
常见问题
是什么?有什么优缺点?核心思想?
是什么?
:SpringBoot是基于Spring的快速开发框架,主要让spring应用程序的开发更加快捷简便,“约定优于配置”,减少大量配置文件和依赖管理工作
优点:
1、自动化配置,开发中更注重业务开发
2、提供了一些starter依赖,可以轻松集成各种框架
3、内置服务器tomcat,不需要额外部署tomcat
核心思想:
约定大于配置
自动配置机制原理?
1、通过@SpringBootConfiguation 引入 @EnableAutoConfiguation 负责自动配置功能; 2、@EnableAutoConfiguation 引入 @Import 3、Spring容器启动时:加载ICO容器会解析@Import注解 4、@Import会导入一个defferedImportSelector组件,延迟导入,会使SpringBoot的自动配置类顺序在最后,方便我们扩展和覆盖 5、然后读取所有的/META-INF/Spring.factories文件 6、过滤出所有的AutoConfiguationClass类型的类 7、最后通过@Condition排除无效的自动配置类; 8、符合条件的Bean被注册到SpringIOC容器中,应用程序可以使用这些自动配置
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心, @EnableAutoConfiguration 给容器导入META-INF/spring.factories 里定义的自动配置类。 筛选有效的自动配置类。 每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能
启动流程?
 
new SpringApplication()
 
主要调用了initialize方法,初始化

1、添加源:通常将配置文件添加到源列表中 2、设置web环境:判断是否应该运行在web环境 3、加载初始化器:从spring.factories文件中加载所有列出的ApplicationContextInitializer实现,并将他们设置到SpringApplication实例中,便于上下文初始化阶段执行他们 4、设置监听器:加载设置ApplicationListener实例,便于响应不同事件 5、确定主应用类:确定主应用类,springboot入口点
SpringApplication.run()

1、启动&停止 计时器:统计启动时长,并打印 2、获取并启动监听器:从spring.factories中解析初始所有的SpringApplicationRunListener 实例,并通知应用的启动过程已经开始 3、装配环境参数:包括配置文件,环境变量,系统属性等 4、打印Banner:启动的横幅 5、创建应用上下文: 6、准备上下文 7、刷新上下文
如何优化启动速度?
1、SpringBoot懒加载

2、创建扫描索引
缓存机制?
@Cacheable、@CachePut 、@CacheEvict ;在方法上使用
应用:某频繁使用的固定信息
如何实现main方法启动web项目?
1、通过SpringApplication类静态方法run来启动web项目 2、当调用run方法的时候,SpringBoot内嵌Tomcat服务器
实现原理: 1、在SpringApplication.run()方法会有一个上下文刷新,最终会调用一个关键方法 onRefresh() 2、接着createWebServer()方法中 通过 TomcatServletWebServerFactory 创建一个Tomcat; 3、最后通过getTomcatWebServer(tomcat) 方法,创建TomcatServer,完成启动 4、及最后的初始化方法中完成启动(初始化方法就在TomcatServer里面)
相比较SpringMVC有什么不一样?
SpringMVC是一个web框架,主要由控制器,视图,映射器组成 SpringBoot是基于Spring的快速开发框架,主要让spring应用程序的开发更加快捷简便,“约定优于配置”,减少大量配置文件和依赖管理工作
最佳实践
1、自定义pom维护第三方依赖(支付,日志采集,mybatis,durid,redis等,统一维护,需要的时候不需要加入版本号)

2、使用springboot自动化starter
 用于排除某个自动化配置 
springboot的主要特性自动化配置,自动化集成后,将繁琐的配置集成到一起,并协同工作
     
web,mybatis,log,test,redis,zuul,kafka等
3、Spring-Initializer来初始化项目
:提供了一个简单的方法来创建项目
4、创建属于自己的spring-boot-starter,基于自动化配置特性 【自定义Starter】

比如公共的组件,抽象出自己的starter,比如统一的API日志,上传文件,支付,reids操作,统一的序列化,统一的权限验证等等,都可以封装成一个starter
创建
1、 依赖 + 配置类

@ConfigurationProperties
2、定义Configuration,并且在其中创建需要的bean

@Configuration @EnableConfigurationProperties(XxlJobProperties.class)
@Bean @ConditionalOnMissingBean :指定该bean在缺失的情况下创建 @conditional0nProperty(prefix = XxlJobProperties,PREFIX, value = "enabled", havingValue = "true") :约定了 当配置了spring.xxl.job.enable=true的时候才会生效
3、创建配置类入口文件

在META-INF/spring 目录下创建一个import文件,内容是 Configuration文件的目录
5、良好的代码,目录结构,不管是更具功能还是业务去划分都可以
6、保证控制器controller 的简洁
1、控制器是无状态,默认单例 2、不应该执行业务逻辑,但可以参数校验相关 3、不把控制器的http层传递到service层 4、控制器围绕用例,业务能力进行 5、合理设计service类 6、业务逻辑和框架分离 7、推荐使用构造器注入实例 8、合理管理配置文件,不同环境不同的文件
场景
如何解决跨域问题?
什么时候需要解决跨域问题?
:域名,协议,端口 中任意一个不同的时候会出现
方法:
1、@CrossOrigin注解(局部配置)

2、JSONP:利用<script>标签,动态创建<script>标签来加载跨域资源
JSONP:JSONP是一种跨域通信的技术,它利用<script>标签可以跨域访问的特性,通过动态创建<script>标签来加载跨域资源,服务器返回一个包含回调函数的JavaScript脚本,客户端通过回调函数处理响应数据。不过JSONP只支持GET请求,且只能用于跨域请求JSON数据。
只能用于跨域请求JSON数据
3、代理服务器
代理服务器:可以通过在同源域名下设置一个代理服务器,实现跨域访问。前端将请求发送给代理服务器,代理服务器再转发请求给目标服务器,并将响应返回给前端,从而绕过跨域限制。这种方式需要部署额外的代理服务器,适用于一些特殊情况。
:同源域名下设置一个代理服务器,实现跨域访问
如何实现定时任务?
1、使用Spring框架的@Schedule注解
2、第三方框架 Quartz
定义job 和 Trigger即可
优雅停机
1、内置功能来优雅的处理应用程序的关闭能力 2、Spring Boot 2.3 开始内置 在 application.properties文件添加一行代码: server.shutdown=graceful 等待请求处理完成的时间配置: spring.lifecycle.timeout-per-shutdown-hpase=2m 默认等待时间是30秒,通过配置可以延长至2分钟