1.redis为什么是单线程?

1
2
redis是单线程的原因在于redis用单个CPU绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的。redis核心就是,如果我的数据全都在内存里,我单线程的去操作就是效率最高的。所以,redis是单线程。
官方答案:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

2.深拷贝和浅拷贝的区别?以及深拷贝最简单的实现方式?

1
2
3
4
浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用
深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
第一种实现方式是重写clone方法来实现深拷贝(实现cloneable接口)
第二种通过对象序列化实现深拷贝(实现Serializable接口)

3.beanFactory和ApplicationContext的区别?

1
2
3
4
5
6
7
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
BeanFactory:BeanFactory是Spring里面最底层的接口,是Ioc的核心,定义了Ioc的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理;
ApplicationContext:ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
①继承MessageSource,因此支持国际化
②资源文件访问,如URL和文件(ResourceLoader)
③载入多个(有继承关系)上下文(及同时加载多个配置文件),使得每一个上下文都专注于一个特定的层次
④提供在监听器中注册bean的事件;

4.并发编程。乐观锁和悲观锁。

1
2
乐观锁:指的是在操作数据的时候非常乐观,乐观地认为别人不会同时修改数据,因此乐观锁默认是不会上锁的,只有在执行更新的时候才会去判断在此期间别人是否修改了数据,如果别人修改了数据则放弃操作,否则执行操作。
指的是在操作数据的时候比较悲观,悲观地认为别人一定会同时修改数据,因此悲观锁在操作数据时是直接把数据上锁,直到操作完成之后才会释放锁,在上锁期间其他人不能操作数据。

5.线程池创建方式有几种?

1
2
3
4
5
6
7
8
线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。

6.类的加载过程

image-20230306144451940

1
2
3
4
5
6
7
8
9
10
读取加载class文件--->链接过程[验证、准备、解析]--->初始化
通过双亲委派模型实现(倚父)
打破双亲委派模型?
1)自定义类加载器,重写loadclass
2)spi机制绕开loadclass方法,当前线程设定关联加载器。spi机制:加载第三方扩展的jar包类初始化
本地磁盘文件Java代码变为的class文件
通过网络下载的class文件
war、jar解压的class文件
从专门的数据库中读取的class文件
使用Java cglib、动态代理生成的代理类class文件

7.包装类和普通类的区别?

1
2
3
普通类有默认值,包装类没有默认值,初始值是null.
普通类在局部变量表里面,包装类在堆里
普通类型可以直接定义,包装类需要new关键字

8.事务的传播机制类型

1
2
通过transactionDefinition类中定义了事务传播的七种类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface TransactionDefinition {
// 如果当前存在事务,那么就加入这个事务,不存在就新建一个事务。
int PROPAGATION_REQUIRED = 0;
// 如果当前有事务,加入事务,如果没有则不使用事务
int PROPAGATION_SUPPORTS = 1;

// 必须在一个事务中执行,如果没有事务,则抛出异常
int PROPAGATION_MANDATORY = 2;

// 不管是否存在事务,都以最新的事务执行,执行完在执行旧的事务
int PROPAGATION_REQUIRES_NEW = 3;

// 表示不支持事务,如果有事务也不加入事
int PROPAGATION_NOT_SUPPORTED = 4;

// 以非事务的方式执行,如果存在事务异常
int PROPAGATION_NEVER = 5;

// 如果调用者不存在事务,那么被调用者自己创建事务,这种情况和REQUIRE一样。
// 如果调用者存在事务,那么被调用者就在调用者的事务里嵌套一个事务,称为嵌套事务。
int PROPAGATION_NESTED = 6;
}

9.AOP有几种通知类型?执行顺序是什么样的?

1
2
3
aop:1.定义切点 2.定义切面逻辑 3.织入
有五种通知类型?
1.前置通知(@Before):设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
1
2
3
4
@Before("pointcut()")
public void before() {
System.out.println("before advice ...");
}
1
2.后置通知(@After):设置当前通知方法与切入点之前的绑定关系,当前通知方法在原始切入点方法后运行
1
2
3
4
@After("pointcut()")
public void after(){
System.out.println("after advice ...");
}
1
3.抛出异常后的通知(@AfterThrowing):设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
1
2
3
4
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("afterThrowing advice ...");
}
1
4.返回后的通知(@AfterReturning):设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
1
2
3
4
@AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("afterReturning advice ...");
}
1
5.环绕通知(@Around):设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
1
2
3
4
@Around("pointcut()")
public void around(){
System.out.println("around advice ...");
}
1
2
3
4
5
6
7
执行顺序:
@Around 前部分业务逻辑
@Before业务逻辑
@AfterReturning业务逻辑
@AfterThrowing业务逻辑
@After业务逻辑
@Around后部分业务逻辑