一、Java 基础
1.1、java 面向对象特征
- 封装
封装类内部的实现结构,保护数据,增加代码可维护性,不需要关心内部实现。
- 继承
从已有的类中派生出新的类。新的类拥有已有类的的属性和行为。并能扩展新的能力。
- 多态
父类的引用指向子类的对象。三要素:继承,重新,父类引用指向子类对象
1.2、Java 中的引用类型有哪几种
强引用
Object o = new Object()
- 1
在这里 o 就是引用,在 Java 中,引用类型有 强、软、若、虚四种引用
强引用内存模型:
没有任何引用指向时被删除
软引用
SoftReference<byte[]> sr = new SoftReference<>(new byte[1024 * 1024 * 10]);
- 1
软引用内存模型:
二流引用,当空间不足时会被清理(一般利用在缓存)
弱引用
WeakReference<Object> wr = new WeakReference<>(new Object());
- 1
内存模型
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
当垃圾回收器发现时就会被回收(使用完就会被删除(spring中 数据库连接对象放在 ThreadLocal 中))
ThreadLocal
ThreadLocal<Object> tl = new ThreadLocal<>();
tl.set(new Object());
- 1
- 2
ThreadLocal set()模型
这里需要看 set()方法源码,在 Entry 中的 key 弱引用指向 ThreadLocal 防止内存泄漏
虚引用
虚引用指向的对象不是给业务人员用的,是给垃圾回收用的(跟踪堆外内存),get 是拿不到的
1.3、hashMap 和 HashTable的区别
- 1、HashTable线程同步,HashMap非线程同步
- 2、HashTable不允许<键,值>有空置,HashTable允许<键,值>有空置
- 3、HashTable使用Enumeration,HashMap使用 Iterator
- 4、HashTable 中 hash 数组的默认大小是11,增加方式 old*2+1 ,HashMap 中hash数组的默认值大小是16,增长方式是2的指数倍
- 5、HashTable 继承Dictionary类,hashMap继承自AbstractMap类
1.4、hashMap 有那些线程安全的方式
1、通过Collections.synchronizedMap(new HashMap<>()) 这种方式获取map
2、重新改写HashMap(可以看 ConcurrentHashMap)
1.5、Java线程中线程的实现方式
- 继承 Thread 类,重 run 方法(缺点,不能多继承)
- 实现 Runnable 接口,实现 run 方法(缺点,没有返回值)
- 实现 Callable 接口,有返回值
- 通过线程池创建
1.6、工作中如何创建线程池
为什么不建议使用 Executors 创建线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 1
- 2
- 3
- 4
- 5
在这里可以看到阻塞队列使用的是 new LinkedBlockingQueue() 这样的一个无界队列(Integer.MAX_VALUE,21亿),这有可能会导致资源耗尽。
更推荐使用以下 new ThreadPoolExecutor() 方式创建线程池,可根据cpu核心数设置适当的线程数
new ThreadPoolExecutor(14, 200, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
- 1
1.7、线程池中有哪几种状态,每种状态表示什么意思
线程池中对应的源码
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;```
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
是用 ctl 这一个变量存储(int是32位,高三位存储状态,后29位代表线程数量)
- RUNNING(运行)线程池新建就会或者调用 execute 方法时就会是此状态,代表运行,能够接受新的任务。
- SHUTDOWN(关闭)调用shutdown()方法后,线程池状态会变成 SHUTDOWN,此时线程池不会在接收新的任务,但会执行已提交等待队列中的任务
- STOP (停止)调用shutdownNow()方法后,线程池状态会变成 STOP ,此时线程池不会在接收新的任务, 不会执行已提交等待队列中的任务
- TIDYING (中间状态)用来给子类扩展
- TERMINATED (终止状态)线程池内的线程都已经终止,线程池进入TERMINATED 状态
1.8、synchronized 和 ReentrantLock 有哪些不同点
以下是对比,前面是synchronized ,后面是 ReentrantLock:
1.9、ThreadLocal底层是如何实现的,有那些应用场景?
介绍: hreadLocal 是Java中所提供的的线程本地存储机制,可以利用该机制将数据存储在某个线程内部
,该线程可以在任意时刻、任意方法中获取缓存数据
底层原理: ThreadLocal 底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都有一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
问题: 如果在线程池中使用ThreadLocal 可能会造成内存泄漏,因为ThreadLocal对象使用完之后,应该把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法,在使用ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清除Entry对象
应用:
1、跨层传递信息的时候。
2、隔离线程,存储一些线程不安全对象,如(SimpleDateFormat)
3、spring中的事务管理器就是用的是ThreadLocal。
4、springmvc 的 HttpSesion,HttpServletRequest、HttpServletResponse都是放在ThreadLocal中,因为Servlet是单例的,而springmvc中允许在controller中通过@Autowired配置requestmresponse以及requestcontext等实例对象,就是搭配ThreadLocal实现线程安全
1.10、ReentrantLock是公平锁还是非公平锁?底层如何实现?
ReentrantLock 可以是公平锁( new ReentrantLock(true)),也可以是非公平锁(new ReentrantLock())
不管是公平锁还是非公平锁,它们的底层实现都会使用AQS来进行排队,它们的区别在于线程使用lock()方法时加锁:
1、如果是公平锁,会检查AQS是否存在线程排队,如果有线程排队,则当前线程也排队
2、如果是非公平锁,不会检查AQS是否存在线程排队,直接竞争锁
注意: 不管是公平锁还是非公平锁,一旦没有竞争到锁,都会进行排队,当锁释放时,都是唤醒排在最前面的线程,所以非公平锁只是体现在了加锁阶段,而没有体现在线程被唤醒阶段。
不管是公平锁还是非公平锁ReentrantLock 都是可重入锁
1.11、synchronized的锁升级过程是怎样的
1、无锁
2、偏向锁: 在锁对象的对象头中记录当前该锁的线程ID,该线程下次如果又来获取该锁姐可以直接获取到,也就是支持锁重入
3、轻量级锁: 当两个或以上线程交替获取锁
,但并没有在对象上并发的获取锁时,偏向锁升级为轻量级锁,线程采用CAS的自旋方式尝试获取锁,避免阻塞线程造成 cpu 在用户态和内核态转换的消耗
4、重量级锁: 两个或以上线程并发的在同一个对象上竞争时,为了避免无用自旋消耗cpu,轻量级锁会升级成重量级锁
1.12、Tomcat中为什么要使用自定义类加载器
应用隔离:
Tomcat作为一个Web容器,能够同时部署和运行多个Web应用程序。每个应用可能依赖不同的库版本或者包含同名类。为了确保每个应用的类库相互独立,避免类冲突,Tomcat为每个Web应用提供了一个独立的类加载器实例,即WebAppClassLoader。这样,即使不同应用中存在相同的类名,它们也是被各自的应用类加载器加载,互不影响。这种机制保证了应用的隔离性,使得不同应用可以独立运行而不会相互干扰。
1.13、对象创建过程(Object o = new Object() )
- 对象创建过程
new 申请空间,半初始化状态,invokespecial 属性赋值,astore_1 o 和 Object建立关联。
- DCL单例到底需不需要 volatile(线程可见,禁止指令重排序)
必须要加volatile,防止指令重排序,拿到半初始化对象(invokespecial ,astore_1 这两条指令有可能发生重排序,导致对象初始化,也就是对象的属性都是默认值没有完成赋值)
1.14、对象在内存中的存储布局
普通对象
- markword
包括,锁信息,GC信息,hashcode - class pointer
指向对应的class文件,前两个叫对象头 - instance data
实例数据 - padding
对齐(空间换取时间,和计算机的64为有关系,会补齐成8的倍数)
数组
这里会多一个 length(数组长度)
对象怎么定位
两种方式,第一种句柄(方便GC),第二种直接指针(效率比较高),hotspot用的是直接指针
对象怎么分配
分代模型
TLAB(线程在伊甸园区分配的空间)
1.15、线程状态
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
以上是 jdk 源码中的线程状态。一共有六种 NEW(新建,cpu无法调度),RUNNABLE(线程启动(对应cpu的就绪/运行)),BLOCKED(获取synchronized锁失败),WAITING(等待,需要手动唤醒),TIMED_WAITING(不需要手动唤醒,执行了sleep等方法),TERMINATED(结束)
1.15、如何停止线程
- stop方法(不用),强制线程停止
- 共享变量停止
- interrupt 方法(Thread.currentThread().interrupt();)
1.16、wait 和 sleep的区别
- sleep属于Thread类的static方法,wait属于Object类的方法
- sleep属于TERMINATED,自动被唤醒,wait 属于 WAITING,需要手动唤醒
- sleep 方法在持有锁时,执行,不会释放锁资源,wait 在执行后会释放锁资源
- sleep可以在持有锁或者不持有锁时执行,wait 必须持有锁时执行
1.17、并发编程三大特性
1、原子性
操作不可分割,不可中断,一个线程执行时其他线程不能影响到它
。
保证方法:
- synchronized
- CAS
- Lock锁
- ThreadLocal
2、可见性
在多线程环境下,如果一个线程修改了一个共享变量的值,那么其他线程应该能够立刻感知到这个修改,而不是继续使用旧值
- volatile
- synchronized
- Lock
- final
3、有序性
`有序性指的是程序执行的顺序按照代码的先后顺序执行,即程序中的指令执行顺序与代码中的书写顺序一致,不会出现乱序执行的情况
- volatile关键字:除了保证可见性外,volatile关键字还可以保证有序性。它禁止了编译器和处理器对volatile变量周围的代码进行重排序优化,从而确保程序执行的顺序性。
- 锁机制:与可见性类似,锁机制(如synchronized关键字或显式锁Lock)也可以保证有序性。在同步块或同步方法中,所有的操作都几乎是串行化执行的,从而避免了指令重排序导致的问题。
- Happens-Before规则:Java内存模型(JMM)定义了一系列的Happens-Before规则,用于规范哪些操作之间的执行顺序是确定的。这些规则包括了程序顺序规则、volatile变量规则、锁规则等,它们共同保证了在并发编程中的有序性。`
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
1.18、什么是CAS
CAS是Compare and Swap的缩写,即比较并交换,它是一种实现无锁数据结构的关键技术。CAS操作包含三个参数:要更新的内存位置(V)、预期值(E)和新值(N)。仅当V值等于E值时,才会将V的值设为N。如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程不进行操作。
优点:避免用户态和内核态切换
缺点:cpu消耗大,存在ABA问题,解决方法追加版本号
1.19、@Contended 注解
@Contended注解是Java 8中引入的一个注解,其主要作用在于减少多线程环境下的“伪共享”现象,从而提高程序的性能。
伪共享(False Sharing):
伪共享是多线程环境中的一种现象,它源于CPU的缓存机制和缓存行的概念。现代CPU为了提高访问效率,会在CPU内部设计快速存储区域,称为缓存(Cache)。缓存不是直接对单个字节进行操作的,而是以块(通常称为“缓存行”)为单位操作的。一个缓存行通常包含64字节的数据。在多线程环境下,如果两个或更多的线程在同一时刻分别修改存储在同一缓存行的不同数据,CPU为了保证数据一致性,会使得其他线程必须等待一个线程修改完数据并写回主内存后,才能继续执行,这种现象就称为伪共享。伪共享会导致不必要的线程等待和性能损耗。
@Contended注解的作用
@Contended注解被设计用来解决伪共享问题。当这个注解被用于类或字段时,它向JVM(Java虚拟机)发出一个提示,表明被注解的类或字段可能存在内存竞争,因此应该尽量将它们放在与其他对象或字段位置隔离的位置。具体来说,这个注解会促使JVM在分配内存时,尽量确保被注解的字段或类的实例独占缓存行,从而避免伪共享现象的发生。
1.20、什么是AQS
AQS全称为AbstractQueuedSynchronizer,是一个用于构建锁和其他同步器的框架。它提供了一个基础的同步原语,允许开发者通过继承AQS并实现其内部定义的抽象方法来实现自定义的同步器。
概述:
AQS在Java并发编程中扮演着重要角色,是构建各种同步机制的基础。它通过一个FIFO(先进先出)的等待队列来管理多线程对共享资源的访问,从而避免了竞态条件和死锁等问题。AQS的核心思想是将多线程的进入和退出操作都放入一个等待队列中,通过对这个队列的管理来控制线程对共享资源的访问。
核心原理:
- 状态变量:AQS内部维护了一个表示同步状态的volatile变量(通常是int类型),通过CAS(Compare-And-Swap)操作来保证这个变量的原子性修改。这个状态变量用于表示锁的状态(如是否被占用)或其他同步器的同步状态。
- 等待队列:AQS还维护了一个FIFO的双向链表,用于存放等待获取资源的线程。当某个线程尝试获取资源失败时,它会被加入到这个队列中等待。当资源被释放时,AQS会按照FIFO的顺序唤醒等待队列中的线程。
二、数据库
2.1、事务的四大特性
1、原子性(Atomicity)
原子性是指事务是一个不可分割的工作单元,事务中的操作要么全部成功,要么全部失败回滚。这意味着,如果事务中的某个操作失败,那么整个事务将被撤销,所有已执行的操作都将回滚到事务开始前的状态。这种特性确保了数据库的完整性和一致性。
2、一致性(Consistency)
一致性是指事务在执行前后,数据库中的数据必须保持一致性和完整性。换句话说,事务执行的结果必须使数据库从一个一致性状态转变到另一个一致性状态。这通常涉及到数据的约束、规则和业务逻辑,以确保数据的准确性和可靠性。例如,在转账操作中,无论转账是否成功,两个账户的总金额应保持不变。
3、隔离性(Isolation)
隔离性是指并发执行的事务之间不会相互干扰,一个事务的执行结果不会受到其他并发事务的影响。数据库系统通过提供不同的事务隔离级别来实现这一特性。这些隔离级别包括读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。不同的隔离级别提供了不同程度的数据保护和一致性保证。
4、持久性(Durability)
持久性是指一旦事务被提交,它对数据库的改变就是永久性的,即使数据库系统发生故障,这些改变也不会丢失。这通常通过将事务的更改记录到持久性存储介质(如磁盘)上来实现。这样,即使系统崩溃或重启,数据库也能恢复到事务提交后的状态,确保数据的可靠性和持久性。
综上所述,事务的四大特性(原子性、一致性、隔离性和持久性)共同确保了数据库系统的可靠性和完整性。这些特性在数据库操作中起着至关重要的作用,是设计和实现数据库系统时需要考虑的关键因素。
2.2、数据库隔离级别
1、读未提交(READ_UNCOMMITTED)
所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
2、读已提交(READ_COMMITTED)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
3、可重复读(REPEATABLE_READ)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
4、串行化(SERIALIZABLE)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
2.3、多版本并发控制(MVCC)
下面将详细解释 mysql mvcc 是怎么实现的
1、版本链的维护
在MySQL中,每个数据行都会维护一个版本链,用于记录该数据行的多个版本。版本链是通过在数据行中添加两个隐藏的字段来实现的:
1、DB_TRX_ID:记录最近一次修改该数据行的事务ID。
2、DB_ROLL_PTR:回滚指针,用于指向该数据行的上一个版本,从而形成一个版本链。
当事务对数据进行修改时,它不会直接覆盖原始数据,而是创建一个新的版本,并将旧版本的数据通过回滚指针链接起来,形成一个版本链。
2、事务的可见性判断
在MVCC中,每个事务看到的数据版本是不同的,这取决于事务的开始时间。当一个事务查询数据时,MySQL会根据事务的开始时间和数据行的版本链来判断哪些版本的数据对该事务是可见的。具体规则如下
- 1、如果数据行的版本的事务ID小于等于当前事务的开始时间,并且该版本没有被其他事务删除,那么这个版本的数据对当前事务是可见的。
- 2、如果数据行的版本的事务ID大于当前事务的开始时间,那么这个版本的数据对当前事务是不可见的。
- 3、如果数据行的版本被其他事务删除,并且删除该版本的事务的提交时间大于当前事务的开始时间,那么这个版本的数据对当前事务也是不可见的。
为了实现上述可见性判断,MySQL在查询时会生成一个Read View(读视图),用于记录当前活跃的事务ID和已经提交的最大事务ID等信息。通过比较数据行版本的事务ID和Read View中的信息,可以确定该版本的数据对当前事务是否可见。
3、undoLog 的作用
在MVCC中,undo log(撤销日志)起到了至关重要的作用。它不仅记录了数据的修改操作,还包含了修改前的数据版本和事务ID等信息。当需要回滚事务或进行快照读时,可以通过undo log来获取数据的旧版本。
此外,undo log还用于支持事务的原子性和持久性。在事务回滚时,MySQL会按照undo log中的记录逆序执行操作,以恢复数据到事务开始前的状态。在事务提交后,即使系统崩溃或重启,也可以通过undo log和redo log(重做日志)来恢复数据到一致的状态。
4、mvcc 实现细节
1、插入操作
当插入一条新记录时,MySQL会为该记录分配一个唯一的事务ID,并将其作为DB_TRX_ID。此时,该记录没有上一个版本,因此DB_ROLL_PTR为空。
2、更新操作
当更新一条记录时,MySQL会先找到该记录的当前版本,并创建一个新的版本。新版本的DB_TRX_ID会记录为当前事务ID,而DB_ROLL_PTR会指向当前版本的旧版本(即上一个版本)。同时,MySQL会将旧版本标记为已删除(实际上是通过设置deleted_bit来实现的),并将其保留在版本链中供其他事务访问。
3、删除操作
删除操作与更新操作类似,也是通过创建一个新的版本来实现的。不过,在删除操作中,新版本的DB_TRX_ID会记录为当前事务ID,而DB_ROLL_PTR会指向被删除记录的当前版本(即上一个可见版本)。同时,MySQL会将被删除的记录标记为已删除,并将其保留在版本链中供其他事务访问(但其他事务无法看到已删除的记录)。
五、MVCC的优点和适用场景
MVCC具有提高并发性能、实现事务的隔离性和减少锁的竞争等优点。它适用于高并发的联机事务处理(OLTP)系统、需要实现事务隔离级别的应用程序和需要支持并发读写操作的应用程序。通过合理地使用MVCC,可以提高数据库的性能和可靠性,满足不同应用场景的需求。
综上所述,MySQL中的MVCC多版本并发控制是一种高效的并发控制机制,它通过维护多个版本的数据和事务的可见性判断来实现并发控制。在了解MVCC的实现原理后,我们可以更好地理解和使用MySQL数据库来处理并发事务。
2.4、ACID 是靠什么保证的
- 原子性:由 undolog 日志来保证,它记录需要回滚的日志信息,事务回滚时撤销执行的 sql
- 一致性:由其他三大特性保证
- 持久性:由 redolog 来保证,mysql 修改数据时会在redolog中记录一份日志数据,就算没有保存成功,只要日志保存成功,数据任然不会丢失
- 隔离性:由mvcc来保证
三、spring
3.1 什么是 IOC,什么是DI,它们有什么关系
一、IOC(控制反转)
定义:
IOC是一种设计原则和编程思想,它涉及到对象创建和依赖管理的方式。在传统的编程方式中,对象通常自行创建或查找它们依赖的对象。而在IOC模式下,这种创建和查找依赖对象的责任从应用程序代码中转移到了外部的第三方(通常是框架或容器),即所谓的“控制权反转”给了容器。
核心思想:
对象不再直接控制其依赖对象的创建和生命周期管理,而是由外部容器负责这些工作。容器知道哪些对象需要哪些依赖,并负责在适当的时候将依赖注入到需要它们的对象中。通过这种方式,对象之间的依赖关系变得更加透明,降低了耦合度,提高了代码的可重用性和可测试性。
二、DI(依赖注入)
定义:
DI是实现IOC的一种具体设计模式或技术手段。它是指将对象所依赖的其他对象(即依赖)通过构造函数、Setter方法或其他方式注入到对象中,从而消除了对象之间的耦合关系。
实现方式:
构造函数注入:在创建对象时,通过构造函数将依赖传递给对象。
Setter方法注入:在对象创建后,通过Setter方法将依赖注入到对象中。
接口注入:通过接口提供的方法将依赖注入到对象中,这种方式在Java中不常见,如Spring中可以通过注解或配置文件来实现。
优点:
降低了类之间的耦合度,提高了代码的可重用性和可维护性。
使得单元测试更加容易,因为可以通过依赖注入将模拟对象或假的依赖注入到被测试的对象中。
三、IOC与DI的关系
关系:
IOC和DI是密切相关的概念。IOC是一种设计原则和编程思想,它描述了对象之间依赖关系的反转。而DI是实现IOC的一种具体技术手段或设计模式。可以说,DI是IOC的具体实现方式之一。
区别:
角度不同:IOC是从宏观角度描述的一种设计原则和编程思想,而DI是从微观角度描述的一种具体实现方式。
侧重点不同: IOC侧重于描述对象之间依赖关系的反转和管理的责任转移,而DI侧重于描述如何将依赖注入到对象中。
综上所述,IOC和DI是面向对象编程中的重要概念,它们共同解决了程序中的依赖管理和解耦问题。通过IOC和DI,我们可以使代码更加灵活、可重用和可测试,从而提高软件的质量和可维护性。
3.2、bean 的生命周期
3.3、BeanFactory 和 ApplicationContext 的区别
- 相同点:
1、都是Java接口,ApplicationContext 继承 BeanFactory (ListableBeanFactory )
2、他们都可以用来配置XML,也支持属性的自动注入
3、都提供getBean(“beanName”) 来获取Bean - 不同点
- 1、BeanFactory 调用getBean 时实例化bean,ApplicationContext 容器启动时实例化
- 2、BeanFactory不支持国际化, ApplicationContext 支持
- 3、如果使用自动注入并使用 BeanFactory ,则需要使用 API 注册 AutoWiredBeanPostProcess,如果使用 ApplicationContext 可以使用XML
3.4、spring 中事务的隔离级别和传播行为
1、隔离级别
@Transactional( isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
spring 中 Transactional 注解的 isolation 是用来配置事务隔离级别的,它的值一共有五种:
- DEFAULT: 默认,也就是这个属性会选择数据库默认的隔离级别。
- READ_UNCOMMITTED: 读未提交,详细介绍看数据库部分
- READ_COMMITTED:读已提交,详细介绍看数据库部分
- REPEATABLE_READ: 可重复读,详细介绍看数据库部分
- SERIALIZABLE: 串行化,详细介绍看数据库部
2、传播行为
spring 中 Transactional 注解的 propagation 是用来配置事务传播行为的,它的值一共有七种:
事务传播行为定义了事务管理器在遇到已存在事务时应如何响应,以及当没有事务存在时应采取何种行动。这些行为是与EJB(Enterprise JavaBeans)事务属性相对应的,但Spring提供了更灵活和广泛的事务管理支持。
- REQUIRED : 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是事务注解的默认设置。
- SUPPORTS : 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。注意: 对于具有事务同步的事务管理器,SUPPORTS与完全没有事务略有不同,因为它定义了一个事务范围,同步将应用于该范围
- MANDATORY : 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW : 创建一个新的事务,并暂停当前的事务(如果存在)。
注意:并非所有事务管理器都支持事务挂起。特别是JtaTransactionManager,它要求jakarta.transaction.TransactionManager可用(这在标准的Jakarta EE中是特定于服务器的) - NOT_SUPPORTED : 以非事务方式执行,并暂停当前的事务(如果存在)。注意:并非所有事务管理器都支持事务挂起。
- NEVER : 以非事务方式执行,如果当前存在事务,则抛出异常
- NESTED : 如果当前存在事务,则在一个嵌套事务内执行;如果当前没有事务,则行为类似于REQUIRED。注意:并非所有事务管理器都支持嵌套事务。默认情况下,这仅适用于DataSourceTransactionManager。一些JTA提供者可能也支持嵌套事务。
四、微服务
4.1、微服务有什么好处
单体项目的缺点:
1、可扩展性差:
随着业务的增长,单体项目的规模会不断增大,导致代码库变得庞大且复杂。
当需要扩展系统的某个特定功能时,往往需要对整个系统进行扩展,这会造成资源的浪费。
难以进行局部优化,因为所有组件都紧密耦合在一起。
2、维护困难:
单体项目中的代码往往相互依赖,修改一个组件可能会影响到其他组件。
随着时间的推移,代码库会变得难以理解和维护。
新加入的开发者需要花费大量时间来熟悉整个系统的架构和代码。
3、高风险:
单体项目中的所有组件都运行在同一个进程中,如果某个组件出现故障,可能会导致整个系统崩溃。
难以进行故障隔离,因为所有组件都共享相同的资源(如内存、数据库连接等)。
4、技术栈受限:
单体项目通常使用统一的技术栈,这限制了开发者在选择新技术时的灵活性。
如果需要引入新技术或框架,可能需要对整个系统进行重构。
5、团队协作难题:
在大型单体项目中,多个团队可能需要同时工作在不同的组件上。
这可能导致代码冲突、依赖关系混乱以及团队协作困难。
微服务项目的优点:
1. 灵活性和可扩展性
独立开发部署和扩展:微服务架构允许每个服务独立开发、测试和部署,这意味着开发团队可以专注于特定服务的实现,而不需要等待整个应用程序的发布周期。
弹性设计:微服务架构能够实现弹性设计,即在面对高并发请求或异常情况时,能够自动调整服务实例数量,以保证系统的可用性和性能。
2. 易于维护
代码复杂度降低: 由于微服务架构将应用程序拆分成多个小型服务,每个服务的功能相对单一,代码复杂度降低,使得服务更易于理解和维护。
独立更新和修复: 如果某个服务出现问题,只需要修复该服务并重新部署,不会影响其他服务的运行,降低了修复和更新的风险。
高效的测试: 每个服务都可以独立进行单元测试和集成测试,提高了测试的效率和准确性,有助于快速发现和修复问题。
3. 技术多样性
选择合适的技术栈: 微服务架构允许开发团队为每个服务选择最适合的技术栈和编程语言,这有助于充分利用各种技术和工具的优势,提高开发效率。
易于尝试新技术: 在微服务架构中,如果某个新技术在某个服务中表现良好,可以逐步推广到其他服务中,降低了尝试新技术的风险和成本。
4. 高可用性和容错性
故障隔离: 微服务架构中的服务之间通过轻量级通信机制进行交互,单个服务的故障通常不会影响其他服务的运行,提高了系统的容错性和可用性。
快速恢复: 由于微服务架构中的服务相对较小且独立,故障排查和修复相对容易,可以快速定位问题并进行修复,减少故障对系统的影响时间。
5. 团队协作
小型团队: 每个服务可以由一个小型的团队负责开发、维护和部署,团队成员之间的沟通和协作更加高效。
明确职责: 每个团队对自己负责的微服务有明确的职责和所有权,这有助于提高团队的责任感和工作效率。