Java面试题整理3

文章目录

  • 前言
  • 一、MySQL
    • [1. 事务](#1. 事务)
    • [2. 锁](#2. 锁)
    • [3. 索引](#3. 索引)
  • 二、Redis
    • [1. 数据类型](#1. 数据类型)
    • [2. 过期策略](#2. 过期策略)
    • [3. 淘汰策略](#3. 淘汰策略)
    • [4. 缓存穿透](#4. 缓存穿透)
    • [5. 缓存击穿](#5. 缓存击穿)
    • [6. 缓存雪崩](#6. 缓存雪崩)
    • [7. 分布式锁](#7. 分布式锁)
  • 三、Spring
    • [1. Spring IoC](#1. Spring IoC)
    • [2. Spring AOP](#2. Spring AOP)
    • [3. Spring MVC](#3. Spring MVC)

前言

牛客论坛项目总结部分常见面试题。


一、MySQL

1. 事务

  1. 事务的特性:原子性、一致性、隔离性、持久性
  2. 事务的隔离性
    (1)并发异常:第一类丢失更新、第二类丢失更新、脏读、不可重复读、幻读
    (2)隔离级别:Read UncommittedRead CommittedRepeatable ReadSerializable
  3. Spring事务管理:声明式事务 、编程式事务

2. 锁

  1. 范围
    (1)表级锁:开销小,加锁快,发生冲突的概率高,并发度低,不会出现死锁。
    (2)行级锁:开销大,加锁慢,发生冲突的概率低,并发度高,会出现死锁。
  2. 类型(InnoDB)
    (1)共享锁(S):行级,读取一行;
    (2)排他锁(X):行级,更新一行;
    (3)意向共享锁(IS):表级,准备加共享锁;
    (4)意向排他锁(IX):表级,准备加排他锁;
    (5)间隙锁(NK):行级,使用范围条件时,对范围内不存在的记录加锁。一是为了防止幻读,二是为了满足恢复和复制的需要。
  3. 加锁
    (1)增加行级锁之前,InnoDB会自动给表加意向锁;
    (2)执行DML语句时,InnoDB会自动给数据加排他锁;
    (3)执行DQL语句时,
    共享锁(S):SELECT ... FROM ... WHERE ... LOCK ... IN SHARE MODE;
    排他锁(X):SELECT ... FROM ... WHERE ... FOR UPDATE;
    间隙锁(NK):上述SQL采用范围条件时,InnoDB对不存在的记录自动增加间隙锁。
  4. 死锁
    (1)场景
    ① 事务1:UPDATE T SET ... WHERE ID = 1; UPDATE T SET ... WHERE ID = 2;
    ② 事务2:UPDATE T SET ... WHERE ID = 2; UPDATE T SET ... WHERE ID = 1;
    (2)解决方案
    ① 一般InnoDB会自动检测到,并使一个事务回滚,另一个事务继续;
    ② 设置超时等待参数 innodb_lock_wait_timeout;
    (3)避免死锁
    ① 不同的业务并发访问多个表 时,应约定以相同的顺序 来访问这些表;
    ② 以批量 的方式处理数据时,应事先对数据进行排序 ,保证线程按固定的顺序来处理数据;
    ③ 在事务中,如果要更新 记录,应申请足够级别的锁,即排他锁。
  5. 悲观锁(数据库)乐观锁(自定义)
    (1)乐观锁的实现:
    ① 版本号机制
    UPDATE ... SET..., VERSION=#{version+1} WHERE ... AND VERSION=${version}
    ② CAS算法(Compare and swap)
    是一种无锁的算法,该算法涉及3个操作数(内存值V、旧值A、新值B),当V=A时,采用原子方式用B的值更新V的值。该算法通常采用自旋操作,也叫自旋锁。
    缺点:
    a) ABA问题:某线程将A改为B,再改回A,则CAS会误认为A没有被修改过;
    b) 自旋操作采用循环的方式实现,若加锁时间长,则会给CPU带来巨大的开销;
    c) 只能保证一个共享变量的原子操作。

3. 索引

B+Tree(InnoDB)

(1)数据分块存储,每一块称为一页;

(2)所有的值都是按照顺序存储的,并且每一个叶子到根的距离相等;

(3)非叶子节点存储数据的节点,叶子节点存储指向数据行的指针;

(4)通过边界缩小数据的范围,从而避免了全表扫描,加快了查找速度。


二、Redis

1. 数据类型

  1. 最大存储数据量
    key:512M
    string:512M
    hash: 2 32 − 1 2^{32}-1 232−1
    list: 2 32 − 1 2^{32}-1 232−1
    set: 2 32 − 1 2^{32}-1 232−1
    srted set
    bitmap:512M
    hyperloglog:12K

2. 过期策略

  1. Redis会把设置了过期时间的key放入一个独立的字典中,在key过期时并不会立刻删除它。Redis会通过如下两个策略来删除过期的key:
    (1)惰性删除
    客户端访问某个key时,Redis会检查该key是否过期,若过期则删除。
    (2)定期扫描
    Redis默认每执行10次过期扫描(配置hz选项),扫描策略如下:
    ① 从过期字典中随机选择20个key;
    ② 删除这20个key中已过期的key;
    ③ 如果过期的key的比例超过25%,重复步骤1。

3. 淘汰策略

  1. 当Redis占用内存超出最大限制(maxmemory)时,可采用如下策略(maxmemory-policy)让Redis淘汰一些数据,以腾出空间继续提供读写服务:
    (1)noeviction:对可能导致增大内存的命令返回错误(大多数写命令,DEL除外);
    (2)volatile-ttl:在设置了过期时间的key中,选择剩余寿命(TTL)最短的key,将其淘汰;
    (3)volatile-lru:在设置了过期时间的key中,选择最少使用的key(LRU),将其淘汰;
    (4)volatile-random:在设置了过期时间的key中,随机选择一些key,将其淘汰;
    (5)allkeys-lru:在所有的key中,选择最少使用的key(LRU),将其淘汰;
    (6)allkeys-random:在所有的key中随机选择一些key,将其淘汰。
  2. LRU算法:维护一个链表,用于顺序存储被访问过的key。在访问数据时,最新访问过的key被移动到表头,即最近访问的key在表头,最少访问的key在表尾。
  3. 近似LRU算法(Redis):给每个key维护一个时间戳,淘汰时随机采样5个key,从中淘汰掉最旧的key。如果还是超出内存限制,则继续随机采样淘汰。优点:比LRU算法节约内存,却可以取得非常近似的效果。

4. 缓存穿透

  1. 场景
    查询根本不存在的数据,使得请求直达存储层,导致其负载过大,甚至宕机。
  2. 解决方案
    (1) 缓存空对象
    存储层未命中后,仍然将空值存入缓存层。再次访问该数据时,缓存层会直接返回空值。
    (2)布隆过滤器
    将所有存在的key提前存入布隆过滤器,在访问缓存层之前,先通过过滤器拦截,若请求的是不存在的key,则直接返回空值。

5. 缓存击穿

  1. 场景
    一份热点数据,它的访问量非常大。在其缓存失效瞬间,大量请求直达存储层,导致服务崩溃
  2. 解决方案
    (1)加互斥锁
    对数据的访问加互斥锁,当一个线程访问该数据时,其他线程只能等待。这个线程访问过后,缓存中的数据被重建,届时其他线程就可以直接从缓存中取值。
    (2) 永不过期
    不设置过期时间,所以不会出现上述问题,这是"物理"上的不过期。
    为每个value设置逻辑过期时间,当发现该值逻辑过期时,使用单独的线程重建缓存。

6. 缓存雪崩

  1. 场景
    由于某些原因,缓存层不能提供服务,导致所有的请求直达存储层,造成存储层宕机。
  2. 解决方案
    (1)避免同时过期
    设置过期时间时,附加一个随机数,避免大量的key同时过期。
    (2)构建高可用的Redis缓存
    部署多个Redis实例,个别节点宕机,依然可以保持服务的整体可用。
    (3)构建多级缓存
    增加本地缓存,在存储层前面多加一级屏障,降低请求直达存储层的几率。
    (4)启用限流和降级措施
    对存储层增加限流措施,当请求超出限制时,对其提供降级服务。

7. 分布式锁

  1. 场景
    修改时,通常需要先将数据读取到内存,在内存中修改后再存回去。在分布式应用中,可能多个进程同时进行上述操作,而读取和修改非原子操作,所以会产生冲突。增加分布式锁,可以解决此类问题。
  2. 基本原理
    (1)同步锁:在多个线程 都能访问到的地方,做一个标记,标识该数据的访问权限。
    (2)分布式锁:在说个进程都能访问到的地方,做一个标记,标识该数据的访问权限。
  3. 实现方式
    (1)基于数据库 实现分布式锁;
    (2)基于Redis 实现分布式锁;
    (3)基于Zookeeper实现分布式锁。
  4. Redis实现分布式锁的原则
    (1)安全性:独享。在任一时刻,只有一个客户端持有锁。
    (2)活性A:无死锁。即便持有锁的客户端崩溃或者网络被分裂,锁仍然可以被获取。
    (3)活性B:容错。只要大部分Redis节点都活着,客户端就可以获取和释放锁。
  5. 单Redis实例实现分布式锁
    (1)获取锁使用命令:
    SET resource_name my_random_value NX PX 30000
    NX:仅在key不存在时才执行成功。
    PX:设置锁的自动过期时间。
    (2)通过Lua脚本释放锁:
    if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
    可以避免删除别的客户端获取成功的锁:
    A加锁 -> A阻塞 -> 因超时释放锁 -> B加锁 -> A恢复 -> 释放锁
  6. 多Redis实例实现分布式锁
    RedLock算法,该算法有现成的实现,其Java版本的库为Redisson。
    (1)获取当前Unix时间,以ms为单位。
    (2)依次尝试从N个实例,使用相同的key和随机值获取锁,并设置响应超时时间。如果服务器没有在规定的时间内响应,客户端应该尽快尝试另外一个Redis实例。
    (3)客户端使用当前时间减去开始获取锁的时间,得到获取锁使用的时间。当且仅当大多数的Redis节点都取到了锁,key的真正有效时间等于有效时间减去获取锁使用的时间。
    (4)如果取到了锁,key真正有效时间等于有效时间减去获取锁使用的时间。
    (5)如果获取锁失败,客户端应该在所有的Redis实例上进行解锁。

三、Spring

1. Spring IoC

  1. Bean作用域
作用域 使用范围 描述
singleton 所有Spring应用 在容器中只存在一个实例,默认值。
prototype 所有Spring应用 在容器中存在多个实例,即每次获取该Bean时,都会创建一个新实例。
request Spring Web应用 为每个请求创建一个新的实例。
session Spring Web应用 为每个会话创建一个新的实例。
globalSession Spring Web应用 为全局的session创建一个实例,只在Protlet上下文中有效。
application Spring Web应用 为整个Web应用创建一个新的实例。

2. Spring AOP

  1. 术语

原文链接:【Spring框架】Aop的六个常用术语+五种通知类型的解析+实例测试

(1)连接点(Joinpoint)

简述:所有可以定义切点的"地方",或者说是所有可以去添加增强代码的"地方"。

详述:是在应用程序执行过程中能够插入切面的一个点,这个点可以是调用方法时,抛出异常时,甚至修改一个字段时,切面代码可以利用这些点插入到应用程序的正常流程中,并增加新的行为。

(2)切点(Pointcut)

需要去添加代码的"地方"。 描述了切面织入的位置。 切点的定义会匹配通知所要织入的一个或多个连接点,可以指定明确的类和方法,也可以使用正则表达式匹配符合条件的类和方法来指定这些切点。

(3)通知(Advice)

向切点处动态添加的那部分代码。 描述了切面要做的工作。

(4)切面(Aspect)

切点+通知。

(5)引入(Introduction)

向现有类添加新方法或属性。

(6)织入(Weaving)

把切面应用到目标对象并创建新的代理对象的过程。 切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进入织入:

① 编译期:切面在目标类编译时被织入。-> 需使用特殊的编译器。

② 类加载期:切面在目标类加载到JVM时被织入。-> 需使用特殊的类加载器。

③ 运行期:切面在应用运行的某个时期被织入,通常在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。-> 需为目标生成代理对象。

3. Spring MVC

  1. 处理请求的过程

    (1)用户发送请求至前端控制器DispatcherServlet;
    (2)DispatcherServlet收到请求调用处理器映射器HandlerMapping;
    (3)处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet;
    (4)DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作;
    (5)执行处理器Handler(Controller,也叫页面控制器);
    (6)Handler执行完成返回ModelAndView;
    (7)HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet;
    (8)DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
    (9)ViewReslover解析后返回具体View;
    (10)DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中);
    (11)DispatcherServlet响应用户。
相关推荐
憨子周1 小时前
2M的带宽怎么怎么设置tcp滑动窗口以及连接池
java·网络·网络协议·tcp/ip
passer__jw7671 小时前
【LeetCode】【算法】3. 无重复字符的最长子串
算法·leetcode
passer__jw7671 小时前
【LeetCode】【算法】21. 合并两个有序链表
算法·leetcode·链表
sweetheart7-71 小时前
LeetCode22. 括号生成(2024冬季每日一题 2)
算法·深度优先·力扣·dfs·左右括号匹配
霖雨2 小时前
使用Visual Studio Code 快速新建Net项目
java·ide·windows·vscode·编辑器
SRY122404192 小时前
javaSE面试题
java·开发语言·面试
Fiercezm3 小时前
JUC学习
java
李元豪3 小时前
【智鹿空间】c++实现了一个简单的链表数据结构 MyList,其中包含基本的 Get 和 Modify 操作,
数据结构·c++·链表
无尽的大道3 小时前
Java 泛型详解:参数化类型的强大之处
java·开发语言
ZIM学编程3 小时前
Java基础Day-Sixteen
java·开发语言·windows