阿里云 Java 后端一面,什么难度?
- 👉《图解网络》 :500 张图 + 15 万字贯穿计算机网络重点知识
- 👉《图解系统》:400 张图 + 16 万字贯穿操作系统重点知识
- 👉《图解MySQL》:重点突击 MySQL 索引、事务、锁、日志等面试高频知识
- 👉《图解Redis》:重点突击 Redis 数据结构、持久化、集群、缓存等面试高频知识
- 👉《Java后端面试题》:涵盖字节、阿里、腾讯、美团、快手、京东、小红书等大厂的面试题
- 👉《吊打面试官》:涵盖消息队列、系统设计、场景题、计算机基础、数据库等面试题
大家好,我是小林。
很多人都有一个疑问,不知道如何在拿到 offer 之后跟 hr 谈薪资。
其实谈薪资最重要的是积攒筹码,那什么是筹码呢?就是你手上的 offer。
如果你拿到了好几个 offer,那已经说明你在市场上被多家公司认可了,这时候谈薪的时候,你会更优势的,而且成功率也会更大一些。
比如你手上拿了 1 个 20k offer,跟下一家谈的时候,就可以喊 23k 的期望薪资,当然还是需要表达一下,你更想加入这家公司。
那么,我们来看看 25 届阿里云开发岗的校招薪资情况如下:
- 32k * 16 + 8w 签字费,同学背景硕士 985,城市北京
- 30k * 16 + 5w 签字费,同学背景硕士 985,城市杭州
- 28k * 16 + 5w 签字费,同学背景硕士 985,城市北京
- 27k * 16,同学背景硕士海龟,城市杭州
- 25k * 16,同学背景硕士985,城市北京
整体年包在 40w-60w,还是非常具有竞争力的,除了薪资之外,还会还有交通补贴和餐补,而且杭州还会有政府的人才补贴。
这次跟大家分享一下阿里云Java 后端的校招面经,这个是电话一面,主要是以拷打技术基础为主,主要是考察了MySQL+Java+Redis+Linux 的知识,由于是电话面,最后是口述算法逻辑。
阿里云面经具体的面试问题如下,大家觉得难得如何呢?
MySQL
事务隔离级别有哪些?
- 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
- 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
- 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
- 串行化(serializable);会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
按隔离水平高低排序如下:
img
针对不同的隔离级别,并发事务时可能发生的现象也会不同。
也就是说:
- 在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象;
- 在「读提交」隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象;
- 在「可重复读」隔离级别下,可能发生幻读现象,但是不可能脏读和不可重复读现象;
- 在「串行化」隔离级别下,脏读、不可重复读和幻读现象都不可能会发生。
幻读和脏读的区别?
脏读
如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。
举个栗子。
假设有 A 和 B 这两个事务同时在处理,事务 A 先开始从数据库中读取小林的余额数据,然后再执行更新操作,如果此时事务 A 还没有提交事务,而此时正好事务 B 也从数据库中读取小林的余额数据,那么事务 B 读取到的余额数据是刚才事务 A 更新后的数据,即使没有提交事务。
img
因为事务 A 是还没提交事务的,也就是它随时可能发生回滚操作,如果在上面这种情况事务 A 发生了回滚,那么事务 B 刚才得到的数据就是过期的数据,这种现象就被称为脏读。
幻读
在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。
举个栗子。
假设有 A 和 B 这两个事务同时在处理,事务 A 先开始从数据库查询账户余额大于 100 万的记录,发现共有 5 条,然后事务 B 也按相同的搜索条件也是查询出了 5 条记录。
img
接下来,事务 A 插入了一条余额超过 100 万的账号,并提交了事务,此时数据库超过 100 万余额的账号个数就变为 6。
然后事务 B 再次查询账户余额大于 100 万的记录,此时查询到的记录数量有 6 条,发现和前一次读到的记录数量不一样了,就感觉发生了幻觉一样,这种现象就被称为幻读。
如何防止幻读?
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
- 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
事务的mvcc机制原理是什么?
MVCC允许多个事务同时读取同一行数据,而不会彼此阻塞,每个事务看到的数据版本是该事务开始时的数据版本。这意味着,如果其他事务在此期间修改了数据,正在运行的事务仍然看到的是它开始时的数据状态,从而实现了非阻塞读操作。
对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。
- 「读提交」隔离级别是在「每个select语句执行前」都会重新生成一个 Read View;
- 「可重复读」隔离级别是执行第一条select时,生成一个 Read View,然后整个事务期间都在用这个 Read View。
Read View 有四个重要的字段:
img
- m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
- min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
- max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
- creator_trx_id :指的是创建该 Read View 的事务的事务 id。
对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
img
- trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;
- roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。
在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:
img
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
- 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。
- 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。
- 如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在 m_ids 列表中:
- 如果记录的 trx_id 在 m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
- 如果记录的 trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。
mysql的什么命令会加上间隙锁?
在可重复读隔离级别下。
- 当你使用非唯一索引进行查询时,InnoDB可能会在索引间隙上加上间隙锁,例如
SELECT * FROM t WHERE a = ? FOR UPDATE;
如果a是非唯一索引,MySQL会在该值的前后加上间隙锁,以防止其他事务在这些间隙插入新记录。 - 在执行带有
WHERE
条件的DELETE
语句时,如果使用的是非唯一索引,MySQL会在符合条件的记录的前后加上间隙锁。 - 类似地,在执行带有
WHERE
条件的UPDATE
语句时,如果使用的是非唯一索引,MySQL也会在符合条件的记录的前后加上间隙锁。
Java
双亲委派机制是什么?
双亲委派机制是Java类加载器(ClassLoader)中的一种工作原理。
它主要用于解决类加载过程中的安全和避免重复加载的问题。在这个机制中,类加载器之间存在层次关系,每个类加载器都有一个父加载器。当一个类需要被加载时,加载请求会从当前加载器开始,逐级向上委托给父加载器,直到根加载器(Bootstrap ClassLoader)。如果根加载器无法加载该类,那么委托链上的每个加载器都会尝试加载,直到找到合适的加载器或者无法加载为止。
双亲委派机制的好处:
- 保证类的唯一性:通过委托机制,确保了所有加载请求都会传递到启动类加载器,避免了不同类加载器重复加载相同类的情况,保证了Java核心类库的统一性,也防止了用户自定义类覆盖核心类库的可能。
- 保证安全性:由于Java核心库被启动类加载器加载,而启动类加载器只加载信任的类路径中的类,这样可以防止不可信的类假冒核心类,增强了系统的安全性。例如,恶意代码无法自定义一个java.lang.System类并加载到JVM中,因为这个请求会被委托给启动类加载器,而启动类加载器只会加载标准的Java库中的类。
- 支持隔离和层次划分:双亲委派模型支持不同层次的类加载器服务于不同的类加载需求,如应用程序类加载器加载用户代码,扩展类加载器加载扩展框架,启动类加载器加载核心库。这种层次化的划分有助于实现沙箱安全机制,保证了各个层级类加载器的职责清晰,也便于维护和扩展。
- 简化了加载流程:通过委派,大部分类能够被正确的类加载器加载,减少了每个加载器需要处理的类的数量,简化了类的加载过程,提高了加载效率。
垃圾回收 cms和g1的区别是什么?
区别一:使用的范围不一样:
- CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用
- G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用
区别二:STW的时间:
- CMS收集器以最小的停顿时间为目标的收集器。
- G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)
区别三: 垃圾碎片
- CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
- G1收集器使用的是“标记-整理”算法,进行了空间整合,没有内存空间碎片。
区别四: 垃圾回收的过程不一样
注意这两个收集器第四阶段得不同
区别五: CMS会产生浮动垃圾
- CMS产生浮动垃圾过多时会退化为serial old,效率低,因为在上图的第四阶段,CMS清除垃圾时是并发清除的,这个时候,垃圾回收线程和用户线程同时工作会产生浮动垃圾,也就意味着CMS垃圾回收器必须预留一部分内存空间用于存放浮动垃圾
- 而G1没有浮动垃圾,G1的筛选回收是多个垃圾回收线程并行gc的,没有浮动垃圾的回收,在执行‘并发清理’步骤时,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理是才会被回收。如果在清理过程中预留给用户线程的内存不足就会出现‘Concurrent Mode Failure’,一旦出现此错误时便会切换到SerialOld收集方式。
spring三级缓存解决循环依赖问题?
循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环,如下图。
循环依赖问题在Spring中主要有三种情况:
- 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
- 第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
- 第三种:通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring都会产生异常。
Spring 解决单例模式下的setter循环依赖问题的主要方式是通过三级缓存解决循环依赖。三级缓存指的是 Spring 在创建 Bean 的过程中,通过三级缓存来缓存正在创建的 Bean,以及已经创建完成的 Bean 实例。具体步骤如下:
- 实例化 Bean:Spring 在实例化 Bean 时,会先创建一个空的 Bean 对象,并将其放入一级缓存中。
- 属性赋值:Spring 开始对 Bean 进行属性赋值,如果发现循环依赖,会将当前 Bean 对象提前暴露给后续需要依赖的 Bean(通过提前暴露的方式解决循环依赖)。
- 初始化 Bean:完成属性赋值后,Spring 将 Bean 进行初始化,并将其放入二级缓存中。
- 注入依赖:Spring 继续对 Bean 进行依赖注入,如果发现循环依赖,会从二级缓存中获取已经完成初始化的 Bean 实例。
通过三级缓存的机制,Spring 能够在处理循环依赖时,确保及时暴露正在创建的 Bean 对象,并能够正确地注入已经初始化的 Bean 实例,从而解决循环依赖问题,保证应用程序的正常运行。
如何使用spring实现事务?
声明式事务管理
使用@Transactional
注解来管理事务比较简单,示例如下:
@Service
public class TransactionDemo {
@Transactional
public void declarativeUpdate() {
updateOperation1();
updateOperation2();
}
}
这样的写法相当于在进入declarativeUpdate()
方法前,使用BEGIN
开启了事务,在执行完方法后,使用COMMIT
提交事务。
也可以将@Transactional
注解放在类上面,表示类中所有的public
方法都开启了事务:
@Service
@Transactional
public class TransactionDemo {
public void declarativeUpdate() {
updateOperation1();
updateOperation2();
}
// 其他public方法...
}
编程式事务管理
相比之下,使用编程式事务要略微复杂一些。
编程式事务管理是通过编写代码来显式地管理事务。这种方式提供了更多的控制,但也会使业务代码变得复杂。
Spring提供了TransactionTemplate
类来简化编程式事务管理。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionTemplate transactionTemplate;
public void addUser(User user) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
userDao.insert(user);
// 其他数据库操作
}
});
}
}
声明式事务 vs 编程式事务
在合适的场景使用合适的方式非常重要,在一些场景下,当对事务操作非常频繁,特别是在递归、外部通讯等耗时的场景中使用事务,很有可能就会引发长事务,那么应该考虑将非事务的部分放在前面执行,最后在写入数据环节时再开启事务。
介绍事务传播模型有哪些?
Spring中规定了7种类型的事务传播特性:
springboot常用注解有哪些?
Bean 相关:
@Component
:将一个类标识为 Spring 组件(Bean),可以被 Spring 容器自动检测和注册。通用注解,适用于任何层次的组件。@ComponentScan
:自动扫描指定包及其子包中的 Spring 组件。@Controller
:标识控制层组件,实际上是 @Component 的一个特化,用于表示 Web 控制器。处理 HTTP 请求并返回视图或响应数据。@RestController
:是 @Controller 和 @ResponseBody 的结合,返回的对象会自动序列化为 JSON 或 XML,并写入 HTTP 响应体中。@Repository
:标识持久层组件(DAO 层),实际上是 @Component 的一个特化,用于表示数据访问组件。常用于与数据库交互。@Bean
:方法注解,用于修饰方法,主要功能是将修饰方法的返回对象添加到 Spring 容器中,使得其他组件可以通过依赖注入的方式使用这个对象。
依赖注入:
@Autowired
:用于自动注入依赖对象,Spring 框架提供的注解。@Resource
:按名称自动注入依赖对象(也可以按类型,但默认按名称),JDK 提供注解。@Qualifier
:与 @Autowired 一起使用,用于指定要注入的 Bean 的名称。当存在多个相同类型的 Bean 时,可以使用 @Qualifier 来指定注入哪一个。
读取配置:
@Value
:用于注入属性值,通常从配置文件中获取。标注在字段上,并指定属性值的来源(如配置文件中的某个属性)。@ConfigurationProperties
:用于将配置属性绑定到一个实体类上。通常用于从配置文件中读取属性值并绑定到类的字段上。
Web相关:
@RequestMapping
:用于映射 HTTP 请求到处理方法上,支持 GET、POST、PUT、DELETE 等请求方法。可以标注在类或方法上。标注在类上时,表示类中的所有响应请求的方法都是以该类路径为父路径。@GetMapping、@PostMapping、@PutMapping、@DeleteMapping
:分别用于映射 HTTP GET、POST、PUT、DELETE 请求到处理方法上。它们是 @RequestMapping 的特化,分别对应不同的 HTTP 请求方法。
其他常用注解:
@Transactional
:声明事务管理。标注在类或方法上,指定事务的传播行为、隔离级别等。@Scheduled
:声明一个方法需要定时执行。标注在方法上,并指定定时执行的规则(如每隔一定时间执行一次)。
介绍NIO BIO AIO?
- BIO(blocking IO):就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。优点是代码比较简单、直观;缺点是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
- NIO(non-blocking IO) :Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
- AIO(Asynchronous IO) :是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
Redis
redis高级数据结构的使用场景
Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
随着 Redis 版本的更新,后面又支持了四种数据类型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五种数据类型的应用场景:
- String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
- List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
- Hash 类型:缓存对象、购物车等。
- Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
- Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
Redis 后续版本又支持四种数据类型,它们的应用场景如下:
- BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
- HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
- GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
- Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
linux
linux常用命令有哪些?
- 文件相关(mv mkdir cd ls)
- 进程相关( ps top netstate )
- 权限相关(chmod chown useradd groupadd)
- 网络相关(netstat ip addr)
- 测试相关(测试网络连通性:ping 测试端口连通性:telnet)
kill -9 9的意义是什么,如何做到强制终止线程
kill -9
是一个Linux/Unix系统中的命令,用于向进程发送信号。-9
代表的是SIGKILL信号,这是一种无法被捕获、忽略或重定义的信号,用于立即终止一个进程。当使用kill -9
命令时,操作系统会立即停止目标进程,不会给进程任何清理或释放资源的机会。
kill
命令是针对进程的,而不是针对线程的。要终止一个线程,你需要知道该线程所属的进程ID(PID)和线程ID(TID)。
以下是几种终止线程的方法:
- 使用
kill
命令终止整个进程:如果你想要终止包含特定线程的整个进程,可以使用kill
命令发送SIGKILL信号给进程。
kill -9 <PID>
- 使用
kill
命令结合ps
和grep
命令查找线程ID:如果你只知道线程的名称或某些特征,可以使用ps
和grep
命令来查找线程ID,然后使用kill
命令终止线程。
ps -eLf | grep <thread_name_or_feature> | grep -v grep | awk '{print $2}' | xargs kill -9
这里的<thread_name_or_feature>
是线程的名称或某些特征。
算法
- 讲一下二叉树最近公共父节点算法
必看图解
- 👉《图解网络》 :500 张图 + 15 万字贯穿计算机网络重点知识
- 👉《图解系统》:400 张图 + 16 万字贯穿操作系统重点知识
- 👉《图解MySQL》:重点突击 MySQL 索引、事务、锁、日志等面试高频知识
- 👉《图解Redis》:重点突击 Redis 数据结构、持久化、集群、缓存等面试高频知识
- 👉《Java后端面试题》:涵盖字节、阿里、腾讯、美团、快手、京东、小红书等大厂的面试题
- 👉《吊打面试官》:涵盖消息队列、系统设计、场景题、计算机基础、数据库等面试题
原文地址:https://blog.csdn.net/qq_34827674/article/details/144693717
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!