Java
阿⾥云 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 的知识,由于是电话⾯,最后是⼝述算法逻辑。
阿⾥云⾯经具体的⾯试问题如下,⼤家觉得难得如何呢?

事务隔离级别有哪些?
读未提交(read uncommitted ),指⼀个事 务还没提交时,它做的变更就能被其他事 务看到;
读提交(read committed ),指⼀个事 务提交之 后,它做的变更才能被其他事 务看到;
可重复读(repeatable read ),指⼀个事 务执⾏过程中看到的数据,⼀直跟这个事 务启动时看到
的数据是⼀致的,MySQL InnoDB 引擎的默认隔离级别;
串⾏化(serializable );会对记录加上读写锁,在多个事 务对这条记录进⾏读写操作时,如果发
⽣了读写冲 突的时候,后访问的事务必须等前⼀个事 务执⾏完成,才能继续执⾏;
按隔离⽔平⾼低排序如下:

针对不同的隔离级别,并发事务时可能发⽣的现象也会不同。
也就是说:

在「读未提交」隔离级别下,可能发⽣脏读、不可重复读和幻读现象;
在「读提交」隔离级别下,可能发⽣不可重复读和幻读现象,但是不可能发⽣脏读现象;
在「可重复读」隔离级别下,可能发⽣幻读现象,但是不可能脏 读和不可重复读现象;
在「串⾏化」隔离级别下,脏读、不可重复读和幻读现象都不可能会发⽣。
幻读和脏读的区别?
脏读
如果⼀个事 务「读到」了另⼀个「未提交事 务修改过的数据」,就意味着发⽣了「脏读」现象。
举个 栗⼦。
假设有 A 和 B 这两个事 务同时在处理,事务 A 先开始从数据库中读取⼩林的余额数据,然后再执
⾏更新操作,如果此时事务 A 还没有提交事 务,⽽此时正好事务 B 也从 数据库中读取⼩林的余额
数据,那么事 务 B 读取到的余额数据是刚才事务 A 更新后的数据,即使没有提交事 务。

因为事 务 A 是还没提交事 务的,也就是它随时可能发⽣回滚操作,如果在上⾯这种情况事务 A 发
⽣了回滚,那么事 务 B 刚才得到的数据就是过期的数据,这种现象就被称为脏读。
幻读在⼀个事 务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不⼀样的情况,就意味着发⽣了「幻读」现象。
举个 栗⼦。
假设有 A 和 B 这两个事 务同时在处理,事务 A 先开始从数据库查询账⼾余额⼤于 100 万的记录,
发现共有 5 条,然后事务 B 也按相同的搜索条件也是查询出了 5 条记录。

接下来,事务 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 有四个重要的字段:

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 存储引擎的数据库表,它的聚簇索引记录中都包含下⾯两个 隐藏列:

trx_id ,当⼀个事 务对某条 聚簇索引记录进⾏改动时,就会把该事务的事务 id 记录在 trx_id 隐藏
列⾥;
roll_pointer ,每次 对某条 聚簇索引记录进⾏改动时,都会把旧版本的记录写⼊到 undo ⽇志
中,然后这个隐藏列是个指针,指向每⼀个旧版本记录,于是就可以通过它找到修改前的记 录。
在创建 Read View 后,我们可以将记录中的 trx_id 划分 这三种情况:

⼀个事 务去访问记录的时候,除了⾃⼰的更新记录总是可⻅之外,还有这⼏种情况:
如果记录的 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 在 DefaultSingletonBeanRegistry 类中维护了三个 重要的缓存 (Map) ,称为“三级缓存”:
singletonObjects (⼀级缓存):存放的是完全初始化好的、可⽤的 Bean 实例, getBean() ⽅法最终返回的就是这⾥⾯的 Bean 。此时 Bean 已实例化、属性已填充、初始化⽅法已执⾏、
AOP 代理(如果需要)也已⽣成。
earlySingletonObjects (⼆级缓存):存放的是提前暴露的 Bean 的原始对象引⽤ 或 早期代理对象引⽤,专⻔⽤来处理循环依赖。当⼀个 Bean 还在创建过程中(尚未完成属性填充和初始
化),但它的引⽤需要被注⼊到另⼀个 Bean 时,就暂时 放在这⾥。此时 Bean 已实例化(调⽤
了构造函数),但属性尚未填充,初始化⽅法尚未执⾏,它可能是⼀个原始对象,也可能是⼀个为了 解决 AOP 代理问题⽽提前⽣成的代理对象。
singletonFactories (三级缓存):存放的是 Bean 的 ObjectFactory ⼯⼚对象。,这是解决循环依赖和 AOP 代理协同⼯作的关键。当 Bean 被实例化后(刚调完构造函数),Spring 会创建
⼀个 ObjectFactory 并将其放⼊三级缓存。这个⼯⼚的 getObject() ⽅法负责 返回该 Bean的早期引⽤(可能是原始对象,也可能是提前⽣成的代理对象),当检测到循环依赖需要注⼊⼀
个尚未完全初始化的 Bean 时,就会调⽤这个⼯⼚来获取早期引⽤。
Spring 通过 三级缓存 和 提前暴露未完全初始化的对象引⽤ 的机制来解决单例作 ⽤域 Bean 的
sette 注⼊⽅式的循环依赖问题。
假设存在两个 相互依赖的单例Bean : BeanA 依赖 BeanB ,同时 BeanB 也依赖 BeanA 。当
Spring 容器启动时,它会按照以下流程处理:
第⼀步:创建 BeanA 的实例并提前暴露⼯⼚。
Spring ⾸先调⽤ BeanA 的构造函数进⾏实例化,此时得到⼀个原始对象(尚未填充属性)。紧接
着,Spring 会将⼀个特殊的 ObjectFactory ⼯⼚对象存⼊第三级缓存( singletonFactories )。这
个⼯⼚的使命是:当其他Bean 需要引⽤ BeanA 时,它能动态返回当前这个半成品的 BeanA (可能
是原始对象,也可能是为应对AOP ⽽提前⽣成的代理对象)。此时 BeanA 的状态是"已实例化但未
初始化",像⼀座刚搭好钢筋⻣架的⼤楼。
第⼆步:填充 BeanA 的属性时触发 BeanB 的创建。
Spring 开始为 BeanA 注⼊属性,发现它依赖 BeanB 。于是容器转向创建 BeanB ,同样先调⽤其构
造函数实例化,并将 BeanB 对应的 ObjectFactory ⼯⼚存⼊三级缓存。⾄此,三级缓存中同时存
在 BeanA 和 BeanB 的⼯⼚,它们都代表未完成初始化的半成品。
第三步: BeanB 属性注⼊时发现循环依赖。
当Spring 试图填充 BeanB 的属性时,检测到它需要注⼊ BeanA 。此时容器启动依赖查找:
在⼀级缓存(存放完整Bean )中未找到 BeanA ;
在⼆级缓存(存放已暴露的早期引⽤)中同样未命中;
最终在三级缓存中定位到 BeanA 的⼯⼚。
Spring ⽴即调⽤该⼯⼚的 getObject() ⽅法。这个⽅法会执⾏关键决策:若 BeanA 需要AOP 代理,则动 态⽣成代理对象(即使 BeanA 还未初始化);若⽆需代理,则直接返回原始对象。得到的这个早期引⽤(可能是代理)被放⼊⼆级缓存( earlySingletonObjects ),同时从三 级缓存清理⼯⼚条⽬。最后,Spring 将这个早期引⽤注⼊到 BeanB 的属性中。⾄此, BeanB 成功持有 BeanA 的引⽤—— 尽管 BeanA 此时仍是个半成品。
第四步:完成 BeanB 的⽣命周 期。
BeanB 获得所有依赖后,Spring 执⾏其初始化⽅法(如 @PostConstruct ),将其转化为完整可⽤
的Bean 。随后, BeanB 被提升⾄⼀级缓存( singletonObjects ),⼆级和三级缓存中关于 BeanB
的临时条⽬均被清除。此时 BeanB 已准备就绪,可被其他对象使⽤。
第五步:回溯完成 BeanA 的构建。
随着 BeanB 创建完毕,流程回溯到最初中断的 BeanA 属性注⼊环节。Spring 将已完备的 BeanB 实
例注⼊ BeanA ,接着执⾏ BeanA 的初始化⽅法。这⾥有个精妙细节:若之前为 BeanA ⽣成过早
期代理,Spring 会直接复⽤⼆级缓存中的代理对象作为最终Bean ,⽽⾮重复创建。最终,完全初
始化的 BeanA (可能是原始对象或代理)⼊驻⼀级缓存,其早期引⽤从⼆级缓存移除。⾄此循环
闭环完成,两个 Bean 皆可⽤。
三级缓存的设计 的精髓:
三级缓存⼯⼚( singletonFactories )负责 在实例化后⽴刻暴露对象⽣成能⼒,兼顾AOP 代理
的提前⽣成;
⼆级缓存( earlySingletonObjects )临时存储已确定的早期引⽤,避免重复⽣成代理;
⼀级缓存( singletonObjects )最终交付 完整Bean 。
整个机制通过中断初始化流程、逆向注⼊半成品、延迟代理⽣成三⼤策略,将循环依赖的死结转
化为有序的接⼒协作。
值得注意的是,此⽅案仅适⽤于Setter/Field 注⼊的单例Bean ;构造器注⼊因必须在实例化前获得
依赖,仍会导致⽆解的死锁。
如何使 ⽤spring 实现事务?
声明式事务管理
使⽤ @Transactional 注解来管理事务⽐较简单,⽰例如下:
@Service
public class TransactionDemo {
@Transactional
public void declarativeUpdate() {
这样的写法相当于在进⼊ 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;
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> 是线程的名称或某些特征。