1、Spring如何处理线程并发问题?
- 在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
- ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
- ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
2、讲述一下spring的AOP和IOC。
AOP(面对切面编程)
- AOP,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect). Spring 使用的动态代理来实现 AOP,主要有两种方式,JDK 动态代理和 CGLIB 动态代理
IOC(控制反转):
- IOC的意思是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。
3、Redis有哪些优缺点?
优点
- 速度快,得益于纯内存操作,单线程配多路复用(6.0是多线程配合多路复用)
- 支持的数据类型丰富,配合高效的数据结构
- 支持持久化,可以进行数据灾难恢复
- 支持主从、哨兵、集群等多种高可用方案
缺点
- redis 执行命令是单线程的,因此遇到时间复杂度高的操作,就会阻塞后续命令的执行,要注意避免
- bigkey 也会引起命令阻塞,尽量避免
4、简述对于缓存穿透的理解与相关解决方案。
认识:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决方案:
- 对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤器,一个一定不存在的数据会被拦截掉,从而避免了对底层存储系统的查询压力。
- 也可以采用一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
5、简述对于缓存雪崩的理解与相关解决方案。
认识:缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
- 避免缓存设置相近的有效期;为有效期增加随机值;统一规划有效期,失效时间均匀分布。
- 使用互斥锁,但会降低吞吐量
- 缓存永不过期,异步更新。
- 优点:不会出现雪崩效应。
- 缺点:不保证一致性,代码复杂度增大(每个value值都要维护异步更新代码),容易堆积垃圾数据。
6、简述对于缓存一致性的理解与相关解决方案。
认识:缓存一致性:保证关系型数据库与非关系型数据库数据一致。
解决方案:
方案1:
查询时先去 redis 中判断数据是否存在,如果存在,则直接返回缓存好的数据。而如果不存在的话,就会去数据库中读取数据,并把数据缓存到 Redis 中
增删改时,先更新库,再让缓存失效(推荐做法,仍可能产生数据不一致)
增删改时,先让缓存失效,再更新库(错误做法,很容易数据不一致)可以给缓存数据加入过期时间,或采用延时双删解决。
方案2:使用 canal 根据 mysql binlog 将数据同步至 redis,客户端代码不需要更新缓存,只做查询缓存操作。
7、你对线程优先级的理解是什么?
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从 1-10),1 代表最低优先级,10 代表最高优先级。
java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如非特别需要,一般无需设置线程优先级。
8、线程wait()和sleep()的区别?
方法归属
sleep(long) 是 Thread 的静态方法,而 wait(),wait(long) 以及 wait(long int) 都是 Object 的成员方法,每个对象都有。
醒来特性
执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来;
wait(long) 除了睡足自己醒之外还可以被 notify 唤醒,wait() 只能被 notify 唤醒。
锁特性
wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入有时限等待状态 TIMED_WAITING;
wait 方法的调用必须先获取 wait 对象的锁(代码片段1),而 sleep 则无此限制;
wait 方法执行后会释放对象锁,允许其它线程获得该对象锁,而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(代码片段2)。
代码片段1
final Object lock = new Object();
synchronized (lock) {
try {
// 不在 synchronized 内调用 wait 会抛出 IllegalMonitorStateException 异常
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
代码片段2
final Object lock = new Object();
synchronized (lock) {
try {
// 在 sleep 期间其它线程可以获得 cpu 的使用权,但无法进入 lock 锁对应的同步代码块
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
之后聊了聊相关的职业规划,这里大家需要注意的是,无论是往哪个方向发展都要有相关的一个证明,大谈特谈很空洞的,而这一点也可以结合对公司的了解来定义自己的规划。
面试到现在不得不感叹,的确有公司就是这样不聊什么问题或者只是聊一些基础的问题就顺利那些offer了。不过我还是相信,有备无患,多准备点总是好的。