JAVA基础
springboot 用法
打包: 使用spring-boot-maven-plugin 插件生成jar包,其中BOOT-INF主要是一些启动信息,包含classes和lib文件,classes文件放的是项目里生成的字节文件class和配置文件,lib文件是项目所需要的jar依赖。
使用jar命令启动。 原理:MANIFEST.MF里有springboot启动类
fork/join用法
和线程池区别
Java 7引入了fork/join框架。它提供了一些工具,通过尝试使用所有可用的处理器核心来帮助加速并行处理二者的作用都是cpu使用率最大化,线程池有个问题,如果一个线程卡,整个作业就卡了。但是fork join不会,一个线程干完事情会窃取别的线程工作。 吞吐量更高。
- forkjoin不适合I/O操作和线程阻塞操作。(如有这个场景使用线程池代替)
RecursiveAction:用于没有返回结果的任务。
RecursiveTask :用于有返回结果的任务。
基本步骤
- 创建ForkJoinPool. ForkJoinPool是执行ForkJoinTask任务的线程池。可以通过默认构造函数创建一个具有默认运行线程数的ForkJoinPool,或者指定线程数来创建。
- 定义ForkJoinTask子类:创建一个继承自ForkJoinTask的类,并实现compute方法。在compute方法中,决定是直接计算结果,还是将任务分解(fork)成更小的任务。
- 分解任务:如果任务足够小,直接计算结果;如果任务较大,将其分解为两个或多个子任务,并递归执行。
- 执行任务:使用ForkJoinPool的invoke或execute方法来启动任务。
- 合并结果:
- 如果任务有返回值,可以通过调用join方法来获取结果。
异常处理
在Java的`Fork/Join`框架中,`isCompletedAbnormally()`方法用于检查一个`ForkJoinTask`是否已经抛出异常或已经被取消。如果任务或其子任务抛出异常,`isCompletedAbnormally()`方法将返回`true`。此外,`getException()`方法可以获取任务抛出的异常。
以下是`isCompletedAbnormally()`方法的用法示例:
public class MySumArray extends RecursiveTask
int start;
int end;
int arr[];
public MySumArray(int start, int end, int[] arr) {
this.start = start;
this.end = end;
this.arr = arr;
}
@Override
protected Long compute() {
long sum = 0;
if(end - start < 3){
for(int i= start;i<end;i++){
sum += arr[i];
}
if(true){
throw new NullPointerException("X");
}
}
else {
int mid = (start+end)/2;
MySumArray s1 = new MySumArray(start,mid,arr);
MySumArray s2= new MySumArray(mid,end,arr);
s1.fork();
s2.fork();
try {
sum = s1.join() + s2.join();
}catch (Exception e) {
// 处理InterruptedException异常
} finally {
if (s1.isCompletedAbnormally()) {
// 如果任务完成异常,打印异常信息
Throwable exception = s1.getException();
System.out.println("Task completed abnormally: " + exception.getMessage());
}
}
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(); // 创建ForkJoinPool
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 示例数组
MySumArray task = new MySumArray( 0, numbers.length,numbers); // 创建任务
long result = pool.invoke(task); // 执行任务并获取结果
System.out.println("Sum: " + result); // 输出结果
long sum = 0;
for(int i= 0;i<numbers.length;i++){
sum += numbers[i];
}
System.out.println(sum);
}
}
在这个示例中,`join()`方法用于等待`ForkJoinTask`任务完成。如果在任务执行过程中抛出异常,`isCompletedAbnormally()`将返回`true`,然后可以通过`getException()`方法获取具体的异常信息并进行处理。这样,即使在`Fork/Join`框架中无法直接捕获子任务的异常,也可以在主线程中进行检查和异常处理。
面向对象SOLID原则
-
- SRP:单一职责原则。
对于单一职责原则,建议是接口一定要做到单一职责原则,类的设计尽量做到只有一个原因引起变化。
- SRP:单一职责原则。
-
- OCP:开闭原则。
开闭原则(OCP)是 Bertrand Meyer在1988年提出的,该设计原则认为:
换句话说,一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展。
- OCP:开闭原则。
-
3.(Liskov Substitution Principle, LSP)
子类必须能够替换其父类
子类不应该改变父类的公共行为。子类可以增加新的方法或属性,但不应该改变父类的公共接口或违反父类的预期行为。 -
- 接口隔离原则(Interface Segregation Principle, ISP):
不应该强迫客户依赖于它们不使用的方法。这意味着应该将大的接口拆分成小的、特定的接口,以满足不同客户的特定需求。
- 接口隔离原则(Interface Segregation Principle, ISP):
-
5 依赖倒置原则(Dependency Inversion Principle, DIP):
高层模块不应该依赖于低层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这个原则强调了在设计时应该依赖于接口或抽象类,而不是具体的实现类。
2. 分布式事务解决方案
-
TCC
原理 https://www.cnblogs.com/duanxz/p/5226316.html
例子:下单场景。 订单服务:创建订单,库存服务:占库存
如果不用TCC可能,库存占用成功因网络超时,订单回滚。库存超占。用了TCC如下。 在try阶段,try失败,调用cancel取消库存占用。
下单,占用销售库存
订单服务 库存服务
Try 创建订单 占库存
Commit 修改订单状态
JDK 8默认的垃圾回收器是什么
在 JDK 8 中,默认的垃圾回收器是 Parallel GC(并行垃圾回收器),也被称为 "吞吐量优先" 垃圾回收器(通过 -XX:+UseParallelGC 属性显式指定)。它主要是为了优化吞吐量,适合于高性能的服务器应用。
g1垃圾回收
设计思想
思想决定高度,对于垃圾回收器也一样,G1 的三个优秀的设计思想,为后面的垃圾收集器(ZGC)奠定了坚实的基础:
基于 Region 的内存布局
面向局部收集的设计思想
GC停顿时间和吞吐量的平衡
Region化整为零,面向局部收集的思想完全碾压了 CMS这种需要收集整个老年代的设计。
基于 Region可以同时兼顾年轻代和老年代的回收,而 CMS只能回收老年代。
基于 Region,因为回收的粒度更细,范围更小,使得 G1的停顿时间更加可预测。
实际生产中,并非全部是非此即彼的选择题,很多时候是即要…又要…,因此,CMS为了追求低延时,牺牲了吞吐量,这显然和这种即又场景格格不入。而 G1则吸取了 CMS的经验教训,尽量做到 GC停顿时间和吞吐量的平衡,它更符合当代大内存的场景需求。
算法
CMS使用的是标记-清除算法,这种算法的最大缺点是产生内存碎片,当内存碎片过多,无法找到足够的连续空间来分配新对象,就会产生一次并发模式失败(Concurrent Mode Failure),启动 Full GC,
而 G1采用的是标记-整理算法,在每次垃圾回收后会进行内存压缩,因此,不会产生内存碎片,便于新生对象的分配。
几种垃圾回收算法选择
- 如果应用程序就是一个小内存(最多大约100 MB),建议使用选项-XX:+UseSerialGC选择串行收集器。
- 如果应用程序将在单个处理器上运行,并且没有暂停时间要求,那么使用选项-XX:+UseSerialGC选择串行收集器。
- 如果应用程序性能、吞吐量是第一优先级,并且没有暂停时间要求,一秒钟或更长时间的暂停都是可以接受的,那么建议使用-XX:+UseParallelGC或-XX:+UseParallelOldGC 选择并行收集器。纯后台程序无交互,并且是多处理器的,推荐考虑-XX:+UseParallelOldGC
- 如果响应时间比总体吞吐量更重要,比如说与用户进行交互的,垃圾收集暂停必须很短,那么可以CMS或G1作为垃圾收集器,-XX:+UseG1GC或-XX:+UseConcMarkSweepGC
- 目前在小内存的应用上,CMS的表现大概率仍会优于G1,而在大内存应用上,G1则大多能发挥其优势,这个优劣势的堆容量平衡点在6-8G之间.
G1垃圾回收算法调优
-XX:InitiatingHeapOccupancyPercent
- 当整个堆占用超过某个百分比时,就会触发并发GC周期,这个百分比默认是45%,我的理解来说,如果你的项目没有大的cpu负载压力,可以适当降低这个值,带来的好处就是提前开始Concurrent Marking Cycle Phases ,进一步来说,回收 年轻代 and 老年代 也会提前开始,这样有利于防止年轻代晋升老年代失败(老年代容量不足)而触发Full GC。
经过观察发现如果这个数值设定过大会导致JVM无法启动并发标记,直接进行FullGC处理。G1的FullGC是单线程,一个22G的对GC完成需要8S的时间,所以这个值在调优的时候写的45%
-
年轻代大小:避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。
-
暂停时间目标:每当对垃圾回收进行评估或调优时,都会涉及到延迟与吞吐量的权衡。G1 GC 是增量垃圾回收器,暂停统一,同时应用程序线程的开销也更多。G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。如果将其与 Java HotSpot VM 的吞吐量回收器相比较,目标则是 99% 的应用程序时间和 1% 的垃圾回收时间。因此,当您评估 G1 GC 的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。当您评估 G1 GC 的延迟时,请设置所需的(软)实时目标,G1 GC 会尽量满足。副作用是,吞吐量可能会受到影响。
-
-XX:+UseStringDeduplication
4. redis 三种集群模式
一个好的集群架构要解决以下几个问题
- 高可用性(High Availability)/容错:通过主从复制和自动故障转移机制,提高了系统的可用性,即使部分节点失效,系统依然可以继续提供服务。
- 线性扩展性(Linear Scalability):可以轻松地通过增加更多的节点来扩展存储容量和处理能力,适应不断增长的数据和请求。
容错性(Fault Tolerance):
这2种特性,redis cluster 比哨兵模式要优秀。
redis cluster特点:去中心化,不需要哨兵集群,Redis-Cluster采用无中心结构,每个节点都保存数据(根据hash槽),节点之间互相连接从而知道整个集群状态。 (客户端连某个节点,就知道hash槽的分布)
https://blog.csdn.net/yangqjiayou/article/details/137627180
最低配置
一个最小的Redis Cluster:需要至少3个主节点。为了提高可用性和容错性,建议配置6个节点:3个主节点和3个从节点
Redis Sentinel的最低配置:至少需要一个Redis主节点和至少一个Redis从节点来构成一个主从复制结构,以及至少三个Sentinel实例来监控这个主从结构,以确保高可用性
。
- 多线程用法,
6。 微服务架构相关技术。熔断,限流,调用链跟踪,分布式事务,
7 目前使用的JVM,springboot,版本
Java 17是一个长期支持(LTS)版本,提供至少三年的官方支持
。如果你需要长期支持和稳定性,Java 17是更好的选择。Java 20是一个非LTS版本,支持周期为六个月。
Spring Boot 3.0.x:需要Java 17,并且兼容至Java 20
。Spring Framework 6.0.11或以上版本也是必需的
java8 lambda原理
Lambda 表达式是 Java 8 引入的一种新的语法糖,它提供了一种简洁的方式来表示可传递给方法或存储在变量中的代码块。本质上,Lambda 表达式是一种匿名函数,即没有名称的函数。它可以被看作是一种更紧凑的方式来实现接口中的抽象方法,特别是那些只有一个抽象方法的接口(称为函数式接口)。
Predicate
Consumer是一个函数式接口,它属于java.util.function包。Consumer接口用于表示一个接受单个输入参数并且不返回结果的操作。它通常用于执行对单个参数的副作用操作,例如打印输出、更新状态或抛出异常。
在Java 8中,str -> str.startsWith("A")是一个Lambda表达式,它代表了一种匿名函数,用于创建实现了Predicate接口的实例。这个Lambda表达式接收一个类型为String的参数str,并返回一个布尔值,表示参数str是否以字母"A"开头。
具体来说,这个Lambda表达式的含义如下:
str:这是Lambda表达式的参数,它是一个String类型的变量,代表将要被检查的字符串。
->:这是Lambda表达式的标志,用于分隔参数和Lambda体。
str.startsWith("A"):这是Lambda体,即Lambda表达式的实现部分。它调用了String类的startsWith方法,检查传入的字符串str是否以指定的前缀"A"开头。如果str以"A"开头,startsWith方法返回true;否则返回false。
关于lambda异常难以排查
限制Lambda的使用范围:
仅在那些不太可能抛出异常或者异常可以被轻易处理的场景中使用Lambda表达式。
- 限制Lambda嵌套深度:
尽量保持Lambda表达式的嵌套层级在合理范围内,通常不超过1层
list.stream()
.filter(item -> item.startsWith("A")) // 第一层
.map(item -> {
return anotherList.stream()
.filter(innerItem -> innerItem.contains(item)) // 第二层
.collect(Collectors.toList());
});
- 避免复杂的逻辑:
不要在Lambda中编写复杂的逻辑。保持Lambda表达式的简洁,只包含简单的操作。
- 使用try-catch块:
在调用Lambda表达式的外部使用try-catch块来捕获和处理异常,这样可以在一个地方集中处理异常。
java
try {
list.forEach(item -> {
// Lambda表达式
});
} catch (Exception e) {
// 处理异常
}
在Java 8中,()->System.out.println是一个Lambda表达式的语法,它定义了一个没有参数的方法,并在方法体内调用System.out.println来打印信息。这种Lambda表达式通常用于实现Runnable接口或者作为参数传递给接受Runnable类型的其他方法。
public class LambdaExample {
public static void main(String[] args) {
// 使用Lambda表达式创建Runnable对象
Runnable runnable = ()-> System.out.println("Hello, World!");
// 启动线程运行Runnable任务
new Thread(runnable).start();
}
}
- 日志记录:
在Lambda表达式中添加日志记录,记录关键变量和执行路径,以便在异常发生时提供更多的上下文信息。
参考资料
https://cloud.tencent.com/developer/article/1870488
optional 用法
适用的场景比较有限。Optional在链式调用时的使用可以避免空指针异常
例子:
optional orElse 函数
If a value is present, returns the value, otherwise returns other.
Params:
other – the value to be returned, if no value is present. May be null.
Returns:
the value, if present, otherwise other
例子
List<User> users = new ArrayList<>();
User user = users.stream().findFirst().orElse(new User("default", "1234"));
https://typeof.tech/post/when-and-how-to-use-optional-in-java/
stram map和filter
map
主要目的是对集合(或流)中的每个元素进行转换操作,将一个元素转换为另一个元素。它关注的是元素的 “变形”,根据提供的转换规则,对每个元素进行操作,返回一个包含转换后元素的新集合(或流)。
例如,有一个包含整数的集合,通过map操作可以将每个整数乘以 2,得到一个新的集合,其中元素都是原来元素的 2 倍。
filter
主要用于从集合(或流)中筛选出满足特定条件的元素,它关注的是元素的 “筛选”。根据给定的条件,判断每个元素是否符合要求,只有符合条件的元素才会被保留下来,最终返回一个包含筛选后元素的新集合(或流)。
例如,从一个包含整数的集合中,通过filter操作可以筛选出所有大于 5 的整数,组成一个新的集合。
JAVA内存模型
Java 内存模型(Java Memory Model,JMM)定义了 Java 程序中的变量、线程如何和主存以及工作内存进行交互的规则。它主要涉及到多线程环境下的共享变量可见性、指令重排等问题,是理解并发编程中的关键概念。
Java 内存模型(Java Memory Model,JMM)定义了 Java 程序中的变量、线程如何和主存以及工作内存进行交互的规则。它主要涉及到多线程环境下的共享变量可见性、指令重排等问题,是理解并发编程中的关键概念。
并发编程的线程之间存在两个问题:
线程间如何通信?即:线程之间以何种机制来交换信息. 1 消息 2 共享变量
线程间如何同步?即:线程以何种机制来控制不同线程间发生的相对顺序
方法内定义的变量,在线程栈里面的,是线程安全的。
每个线程都拥有自己的工作内存,工作内存是线程私有的。
栈、本地方法栈、程序计数器这三个部分都是线程独占的,线程安全
堆内存和方法区是公共的,非线程安全
volatile通过在volatile变量的操作前后插入内存屏障的方式,保证了变量在并发场景下的可见性和有序性。无法保证原子性
原子性保证:使用lock,synchronized。
https://www.cnblogs.com/hollischuang/p/11386988.html
洗牌算法,比如考试抽题
思路:对数组做随机swap
特点:当数组超过5个长度,直接用list swap,超过转数组swap再转回list
浅拷贝,深拷贝
浅拷贝(Shallow Copy)
浅拷贝创建一个新对象,其字段值与原始对象相同。如果字段是基本数据类型,则拷贝的是基本数据类型的值;如果字段是对象的引用,则拷贝的是引用,而不是引用的对象本身。因此,如果引用指向的对象发生变化,浅拷贝出来的对象相应的字段也会变化。
实现浅拷贝的方式:Object.Clone
深拷贝:一般使用序列化和反序列化来做,Jackson库 DeepCopyUtils.deepCopy
限流算法
- 固定窗口
计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。
自己实现:定时任务+redis incr原子自增性
缺点:控制的流速不均衡。
比如假设1min内服务器的负载能力为100,因此一个周期的访问量限制在100,然而在第一个周期的最后5秒和下一个周期的开始5秒时间段内,分别涌入100的访问量,虽然没有超过每个周期的限制量,但是整体上10秒内已达到200的访问量,已远远超过服务器的负载能力,由此可见,计数器算法方式限流对于周期比较长的限流,存在很大的弊端。
分布式限流算法:Redisson RateLimiter
https://blog.csdn.net/weixin_41846320/article/details/95941361
https://juejin.cn/post/6889285100128305160
guava--RateLimiter源码分析
https://www.cnblogs.com/duanxz/p/14659528.html
NIO VS AIO
NIO(New I/O)和AIO(Asynchronous I/O)是Java中两种不同的I/O处理方式,它们的主要区别如下:
概念与原理
- NIO:也称为非阻塞I/O,它是基于通道(Channel)和缓冲区(Buffer)进行I/O操作的。在NIO中,通道是双向的,可以进行读写操作,而缓冲区则用于存储数据。NIO采用了多路复用器(Selector)来实现一个线程管理多个通道,通过轮询的方式来查看通道是否有数据可读或可写,从而实现非阻塞的I/O操作。
- AIO:即异步I/O,它是基于事件和回调机制实现的。在AIO中,当发起一个I/O操作后,线程不会阻塞等待操作完成,而是继续执行其他任务。当I/O操作完成后,系统会通过回调的方式通知应用程序,应用程序再在回调函数中处理I/O操作的结果。
性能特点
- NIO:在处理大量并发连接时,NIO的性能相对传统的阻塞I/O有较大提升。因为它通过一个线程管理多个通道,减少了线程上下文切换的开销。但是,NIO在高并发情况下,如果有大量的I/O事件需要处理,可能会出现轮询效率问题,导致性能下降。
- AIO:AIO的性能在高并发情况下通常比NIO更优,尤其是在处理大量并发的I/O操作且每个操作的响应时间较长时。因为它不需要线程不断地轮询查看I/O操作是否完成,而是在I/O操作完成后通过回调机制通知应用程序,从而避免了不必要的线程等待和轮询开销。
编程模型
- NIO:NIO的编程相对复杂一些,需要开发者手动管理通道、缓冲区和多路复用器等。例如,需要通过Selector的select()方法来轮询通道的状态,然后根据通道的就绪状态进行相应的读写操作。
- AIO:AIO的编程模型相对简单,因为它基于事件和回调机制,开发者只需要定义好回调函数,在I/O操作完成后系统会自动调用回调函数来处理结果。
适用场景
- NIO:适用于连接数较多但每个连接的读写操作相对不频繁的场景,如网络服务器、消息中间件等。例如,在一个Web服务器中,可能会有大量的客户端连接,但每个客户端可能并不是一直在发送或接收数据,这时使用NIO可以有效地管理这些连接,提高服务器的并发处理能力。
- AIO:适用于连接数较多且每个连接的I/O操作都比较耗时的场景,如文件下载、数据库查询等。例如,在一个文件下载服务器中,当多个客户端同时下载文件时,使用AIO可以在文件读取完成后通过回调函数通知客户端,而不需要线程一直等待文件读取完成,从而提高服务器的并发处理效率。
JVM内存泄漏场景和规避方法
-
数据库连接或文件流未关闭:小疏忽可能导致大问题,内存被锁死。
对应办法: 正确关闭资源
使用 try-with-resources 语句:对于实现了AutoCloseable接口的资源,如文件流、数据库连接等,尽量使用try-with-resources语句来确保资源在使用完毕后自动关闭。
手动关闭资源:对于一些无法使用try-with-resources语句的情况,如使用第三方库提供的资源对象,需要在使用完毕后通过调用相应的close()方法手动关闭资源,并在finally块中进行关闭操作,以确保资源一定会被关闭。 -
静态变量持有对象引用
对策:可以在不再需要使用staticList中的对象时,手动将其清空或根据实际情况合理地移除不需要的元素。 比如使用本地缓存,用完了要把对象清掉。 -
监听器和回调函数未移除:短生命周期对象被长生命周期对象拖住。
对策:可以在短生命周期对象不再需要使用时,将其从长生命周期对象的监听器列表中移除,例如: -
缓存数据无限增长:缓存容量不设限,数据堆积如山,最终撑爆内存
对策:为缓存数据设置容量上限,例如使用LRU清理策略。如GuavaCache Guava Cache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。
线程状态
1.线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中
断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方
法一般用在同步方法或同步代码块中。
2.线程睡眠(sleep)
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占
有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法
会导致当前线程进入 WATING 状态.
使用的范围: wait():必须在同步代码块中使用; sleep():可以在任何地方使用;
是否需要捕获异常 wait():不需要捕获异常; sleep():需要捕获异常;
在使用jstack查看线程状态时,以下几种线程状态如果出现较多可能存在潜在风险:
BLOCKED状态
- 当大量线程处于 BLOCKED 状态时,这意味着线程在等待获取锁来进入同步块或方法。可能是因为锁竞争激烈,系统设计可能存在问题,比如锁的粒度太粗,导致很多线程在等待获取同一个锁,从而使系统性能下降。
WAITING状态
- 大量线程处于 WAITING 状态(如调用 Object.wait 、 Thread.join 、 LockSupport.park 等方法进入等待)可能表示程序逻辑过度依赖这些等待机制,或者等待的条件没有被正确、及时地触发。这会导致线程长时间闲置,资源无法有效利用。
TIMED_WAITING状态
- 尽管 TIMED_WAITING 是有等待时间限制的等待状态,但如果大量线程都处于这种状态,也可能暗示着系统中有许多线程在频繁地等待某些资源或者条件,等待超时时间设置不合理或者等待资源不能及时释放等情况,会影响系统的吞吐量和响应速度。
Spring 事务传播机制
常见的事务传播行为
- REQUIRED(默认):如果当前没有事务,就新建一个事务;如果当前已经存在事务,则加入该事务。例如,方法 A 调用方法 B,方法 A 已经开启了事务,那么方法 B 就会加入到方法 A 的事务中一起执行,它们在同一个事务里,要么都成功提交,要么都回滚。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。比如,在一个事务方法中调用了另一个使用该传播机制的方法,被调用方法会加入调用者的事务;若在非事务环境下调用它,它就以普通方法执行,不会开启新事务。
- MANDATORY:要求当前必须存在事务,如果不存在事务则抛出异常。主要用于必须在事务环境中执行的方法,确保方法在事务的控制下执行,以保证数据的一致性。
- REQUIRES_NEW:无论当前是否存在事务,都会新建一个事务。如果当前存在事务,会将当前事务挂起,等新事务执行完毕后再恢复原事务。例如,方法 A 调用方法 B,方法 B 使用该传播机制,那么方法 B 会开启一个新事务独立执行,其执行结果不会影响方法 A 的事务。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则将当前事务挂起。通常用于不需要事务或者不应该在事务中执行的方法,如一些查询操作或对外部系统的调用等。
- NEVER:不允许在事务环境中执行,如果当前存在事务,则抛出异常。用于确保方法绝对不会在事务中运行,适用于一些与事务完全无关的操作。
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务。嵌套事务是外部事务的一个子事务,外部事务回滚时,嵌套事务也会回滚;而嵌套事务回滚时,外部事务可以选择继续提交或回滚。
JAVA集合
LinkedHashMap
它是有序的hashmap,非线程安全
链表结构:LinkedHashMap 在 HashMap 的基础上,通过额外维护一个双向链表来记录元素的顺序。这个双向链表的节点包含了键值对的引用,每个节点都有指向前一个节点和后一个节点的引用。这使得 LinkedHashMap 能够在保持高效存储和查找的同时,还能维护元素的顺序。
线程安全版本:ConcurrentLinkedHashMap
是Google提供的一种容器(Guava),作为ConcurrentHashMap的封装,适用于实现LRU缓存策略。当达到预设的最大容量时,它会淘汰最不常使用的数据。
LinkedHashMap vs TreeMap
适用场景
LinkedHashMap:
适用于需要高效查找,同时又要维护元素的插入顺序或者访问顺序的场景。比如缓存系统(可以根据访问顺序淘汰最久未访问的数据)、记录操作历史(按照操作发生的时间顺序记录)等。
TreeMap:
适用于需要对键进行排序存储,并且经常需要进行范围查询(如查找键大于某个值或小于某个值的所有元素)的场景。例如,存储学生成绩,按照成绩排序,方便查询成绩排名、某个分数段的学生人数等情况。
有序SET
ConcurrentSkipListSet 线程安全 和TreeSet非线程安全
有序Map
TreeMap 线程不安全。 ConcurrentSkipListMap(线程安全)
如何给线程池的线程传递 threadlocal变量
使用spring线程池套件ThreadPoolTaskExecutor
设置一个装饰器,
executor.setTaskDecorator(new AuditAsyncTaskDecorator());
TaskDecorator是在一些异步编程框架中用于装饰任务(task)的工具。它允许你在任务执行前后添加自定义的逻辑。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南