抛开实际谈理论永远是流氓,开设此专栏的目的很简单,就是聚焦于从面试题中回顾知识,里面概括了我在面试中遇到的真实面试题以及收录其他同路人的面经,希望能在面试题中再次回顾知识,能把会的东西表达出来。参考答案来源包括但不仅限于:个人总结、JavaGuide小林coding,咕泡教育
持续更新,直至上岸

这几篇老文章都是我之前用飞书云文档写的,因转换格式问题,本文之前已写完的文档图片太多了,不想一一转化了,大家可以访问我的飞书云文档来查看这些图片–https://miu7shl031o.feishu.cn/drive/folder/WJiBfqX9klvKNEdpTHccfrEQn3e

某厂后端java实习一面

  1. 聊一下关于mp的使用

MyBatis-Plus是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。如果添加了MybatisPlus的依赖 但是不想使用MybatisPlus的类和方法 这时候仍然可以按照之前使用Mybatis的步骤做开发 几乎和之前没有任何的改变

它的使用方法

① 导入依赖,如果有注释掉之前mybatis的依赖

②如果MyBatis Plus有自定义mapper文件,地址配置由mybatis-mapper-locations改为mybatis-plus.mapper-locations

③ 给实体类加注解

@TableName(“user”) 加载类上 将该类和数据库中对应的表建立联系 注解值为表名

@TableId(value = “对应表列名”,type = IdType.AUTO) 建立主键属性和主键字段之间的联系。 value 字段名, type 定义主键自增类型 IdType.AUTO MySQL自动递增

常规属性@TableField(“name”) 将常规属性和常规字段名建立联系

连表查询 封装数据的属性 没有对应的字段 需要定义忽略

@TableField(exist = false) exist = false代表当前属性在表中没有字段名

  1. HashMap的底层讲一下

我从三个方面讲一下它

1.HashMap 中的关键属性—看-HashMap 的源码片段,可以知道它的几个属性

第一个属性 loadFactor,它是负载因子,默认值是 0.75,表示扩容前。

第二个属性 threshold 它是记录 HashMap 所能容纳的键值对的临界值,它的计算规则是负载因子乘以数组长度。

第三个属性 size,它用来记录 HashMap 实际存在的键值对的数量。

第四个属性 modCount,它用来记录 HashMap 内部结构发生变化的次数。

第五个是常量属性 DEFAULT_INITIAL_CAPACITY ,它规定 的默认容量是 16。

2、HashMap 的存储结构

HashMap 采用的是key-value 的存储结构。HashMap 的数组部分称为 Hash 桶,数组元素保存在一个叫做 table 的属性中。jdk1.8以后当链表长度大于等于 8 时,链表数据将会以红黑树的形式进行存储。

每个 Node 节点,保存了用来定位数组索引位置的 hash 值、Key、Value 和链表指向 的下一个 Node 节点。而 Node 类是 HashMap 的内部类,它实现了 Map.Entry 接口, 它的本质其实可以简单的理解成就是一个键值对。

  1. HashMap 的工作原理

首先,通过hash算法计算出key的hashcode值,然后根据数组长度取模,得到在数组中的位置。

如果该位置上没有元素,则直接将key-value存储在该位置上。

如果该位置上已经存在元素,则需要进行链表或红黑树的操作。

如果链表长度小于8,则采用链表存储,将新元素插入到链表尾部。

如果链表长度大于等于8,则将链表转化为红黑树进行存储。

当数组长度达到阈值时(默认为0.75),需要进行扩容操作,将数组长度扩大为原来的两倍,并将原有元素重新分布到新数组中。

当数组中的元素数量小于阈值的0.25时,会进行缩容操作,将数组长度缩小为原来的一半。

  1. mysql的锁了解吗

我通常会把Mysql中锁搞成六种分类,共14种锁

  1. 死锁

死锁,简单来说就是两个或者两个以上的线程在执行的过程中,争夺同一个共

享资源造成的相互等待的现象

互斥条件,共享资源 X 和 Y 只能被一个线程占用;

请求和保持条件,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不

释放共享资源 X;

不可抢占条件,其他线程不能强行抢占线程 T1 占有的资源;

循环等待条件,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有

的资源,就是循环等待

  1. 锁的区间划分

1、间隙锁

间隙锁是开区间的,是一个在索引记录之间的间隙上的锁。

作用:保证某个间隙内的数据在锁定情况下不会发生任何变化。比如mysql默认隔离级别下的可重复读(RR)。 当使用唯一索引来搜索唯一行的语句时,不需要间隙锁定。如下面语句的id列有唯一索引,此时只会对id值为10的行使用记录锁。

2、临键锁(Next-key Locks)

临键锁是行锁+间隙锁,即临键锁是是一个左开右闭的区间,比如(- ∞, 1 ] |(1, 3 ] |(3, 4 ] | (4, + ∞)。

InnoDB的默认事务隔离级别是RR,在这种级别下,如果使用select … in share mode或者select … for update语句,那么InnoDB会使用临键锁,因而可以防止幻读;但即使你的隔离级别是RR,如果你这是使用普通的select语句,那么InnoDB将是快照读,不会使用任何锁,因而还是无法防止幻读。

4.3 锁的粒度划分

  1. 表级锁(Table-level lock)

InnoDB在使用过程中只要不通过索引检索数据时,全部是表锁。 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低InnoDB在使用过程中只要不通过索引检索数据时,全部是表锁。 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低

  1. 行级锁(Record Locks)

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点,MySQL于Oracle不同,后者是通过在数据块中对相应的数据行加锁来实现的,InnoDB只有通过索引条件检索数据,InnoDB才使用行级锁

行锁的劣势:开销大;加锁慢;会出现死锁

行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强

3、页级锁

页级锁是 MySQL 中比较独特的一种锁定级别,在其他数据库管理软件中并不常见。 页级锁的颗粒度介于行级锁与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力同样也是介于上面二者之间。另外,页级锁和行级锁一样,会发生死锁。 页级锁主要应用于 BDB 存储引擎。

4.4 锁级别划分

1、共享锁(share lock,即S锁)

共享锁(S):又称读锁,允许一个事务去读取一行,阻止其他事务获得相同数据集的排它锁,若事务T对数据对象A加上S锁,则事务T可以读A,但不能修改A,其他事务只能对再对A加S锁,而不能加X锁,直到T释放A上的锁,这保证了其他事务可以读A,但在释放A上的S锁之前不能对A做任何修改。

2、排它锁 / 独占锁(exclusive lock,即X锁)

排它锁(X):又称写锁,允许获取排它锁的事物更新数据,阻止其他事务取得相同的数据集共享读锁和排它写锁,若事务T对数据对象A加上X锁,事物T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T 释放A上的锁

3、意向锁

事物B对一行数据使用行锁,当有另一个事物A对这个表使用了表锁,那么这个行锁就会升级为表锁,事务A在申请行锁(写锁)之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,因为表上有意向排他锁之后事务B申请表的写锁时会被阻塞。

当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。

4.5 加锁方式划分

1、自动锁( Automatic Locks)

当进行一项数据库操作时,缺省情况下,系统自动为此数据库操作获得所有有必要的锁。

自动锁分为三种:

DML 锁:

锁用于控制并发事务中的数据操纵,保证数据的一致性和完整性。

保护并发情况下的数据完整性。

语句能够自动地获得所需的表级锁(TM)与行级(事务)锁(TX)。

DDL 锁

锁用于保护数据库对象的结构,如表、索引等的结构定义。

排它 DDL 锁

创建、修改、删除一个数据库对象的 DDL 语句获得操作对象的 排它锁。

共享 DDL 锁

需在数据库对象之间建立相互依赖关系的 DDL 语句通常需共享获得 DDL锁。

如创建一个包,该包中的过程与函数引用了不同的数据库表,当编译此包时该事务就获得了引用表的共享 DDL 锁。如使用 alter table 语句时,为了维护数据的完成性、一致性、合法性,该事务获得一排它 DDL 锁

2.systemlocks 系统锁。

3.显示锁(LOCK TABLES )

某些情况下,需要用户显示的锁定数据库操作要用到的数据,才能使数据库操作执行得更好,显示锁是用户为数据库对象设定的。

4.6 锁的使用方式划分

1、乐观锁(Optimistic Lock)

乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。

2、悲观锁(Pessimistic Lock)

悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。

  1. 索引为什么用B+树,有什么特性

首先,常规的数据库存储引擎,一般都是采用 B 树或者 B+树来实现索引的存储。

因为 B 树是一种多路平衡树,用这种存储结构来存储大量数据,它的整个高度会相比二叉树来说矮很多。 而对于数据库来说,所有的数据必然都是存储在磁盘上的,而磁盘 IO 的效率实际上是 很低的,特别是在随机磁盘 IO 的情况下效率更低。 所以树的高度能够决定磁盘 IO 的次数,磁盘 IO 次数越少,对于性能的提升就越大,这 也是为什么采用 B 树作为索引存储结构的原因。

但是在 Mysql 的 InnoDB 存储引擎里面,它用了一种增强的 B 树结构,也就

是 B+树来作为索引和数据的存储结构。

相比较于 B 树结构,B+树做了几个方面的优化。

\1. B+树的所有数据都存储在叶子节点,非叶子节点只存储索引。

\2. 叶子节点中的数据使用双向链表的方式进行关联

使用 B+树来实现索引的原因,我认为有几个方面。

\3. B+树非叶子节点不存储数据,所以每一层能够存储的索引数量会增加,意味着 B+ 树在层高相同的情况下存储的数据量要比 B 树要多,使得磁盘 IO 次数更少。

\4. 在 Mysql 里面,范围查询是一个比较常用的操作,而 B+树的所有存储在叶子节点的数据使用了双向链表来关联,所以在查询的时候只需查两个节点进行遍历就行,而 B 树需要获取所有节点,所以 B+树在范围查询上效率更高。

\5. 在数据检索方面,由于所有的数据都存储在叶子节点,所以 B+树的 IO 次数会更加稳定一些。

\6. 因为叶子节点存储所有数据,所以 B+树的全局扫描能力更强一些,因为它只需要扫描叶子节点。但是 B 树需要遍历整个树。

另外,基于 B+树这样一种结构,如果采用自增的整型数据作为主键,还能更好的避免增加数据的时候,带来叶子节点分裂导致的大量运算的问题。

总的来说,我认为技术方案的选型,更多的是去解决当前场景下的特定问题,并不一定是说 B+树就是最好的选择,就像MongoDB 里面采用B树结构,本质上来说,其实是关系型数据库和非关系型数据库的差异

  1. 创建索引需要考虑什么,索引优化有没有相关经验

(1)MySQL 表主键设计

自增: 顺序存储,索引维护成本低,索引效率高;

UUID:非顺序增长,随机IO严重。

(2)索引并不是越多越好,要根据查询,有针对性的创建

(3)应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描;

(4)值分布很稀少的字段不适合建索引,例如“性别”这种只有两三个值的字段;

(5)不用外键,由程序保证约束;

(6)尽量不用UNIQUE,由程序保证约束;

(7)使用多列索引时注意顺序和查询条件保持一致,同时删除不必要的单列索引。

(8)排序时,排序字段需要注意index,尤其是关联查询排序时,尽可能使用小表的字段进行排序

SQL优化原则

(1)避免属性隐式转换 , 如定义Moblie varchar where Moblie =198989888会导致全表扫描;

(2)Where子句中条件字段本身避免使用函数;

(3)使用获取的必要字段代替SELECT *;

(4)批量插入,使用INSERT INTO table (col1,col2,…) VALUES (value1, value2,…),(value1, value2,…); 插入多条数据只有一次提交;

(5)避免使用长事务;

(6)禁止负向查询: NOT、!=、<>、!<、!>、NOT IN、NOT LIKE,会导致全表扫描;

(7)大表之间的join,尽量缩小结果集之后再join,否则会消耗较多的内存和CPU;

(8)搜索严禁左模糊或者全模糊(like %XX, 或like %XX%),会导致全表扫描。

7.用过linux吗?Linux的常用命令有哪些

切换用户(su)

切换目录(cd)

查看目录(ls)

创建目录(mkdir)

删除目录与文件(rm)

修改目录(mv)

拷贝目录(cp)

搜索目录(find)

查看当前目录(pwd)

新增文件(touch)

删除文件(rm)

编辑文件(vi、vim)

过滤文件(grep)

滴滴 一面&二面

  1. 说一下MySQL 优化有哪几方面

MySQL 的性能优化我认为可以分为 4 大部分

硬件和操作系统层面的优化

架构设计层面的优化

MySQL 程序配置优化

SQL 优化

从硬件层面来说,影响 Mysql 性能的因素有,CPU、可用内存大小、磁盘读写速度、

网络带宽从操作系层面来说,应用文件句柄数、操作系统网络的配置都会影响到 Mysql 性能。 这部分的优化一般由 DBA 或者运维工程师去完成。

架构设计层面的优化

MySQL 是一个磁盘 IO 访问量非常频繁的关系型数据库

在高并发和高性能的场景中.MySQL 数据库必然会承受巨大的并发压力,而此时,我们

的优化方式可以分为几个部分。

\1. 搭建 Mysql 主从集群,单个 Mysql 服务容易单点故障,一旦服务器宕机,将会导

致依赖 Mysql 数据库的应用全部无法响应。 主从集群或者主主集群可以保证服务

的高可用性。

\2. 读写分离设计,在读多写少的场景中,通过读写分离的方案,可以避免读写冲突导

致的性能影响

\3. 引入分库分表机制,通过分库可以降低单个服务器节点的 IO 压力,通过分表的方

式可以降低单表数据量,从而提升 sql 查询的效率。

\4. 针对热点数据,可以引入更为高效的分布式数据库,比如 Redis、MongoDB 等,

他们可以很好的缓解 Mysql 的访问压力,同时还能提升数据检索性能

MySQL 程序配置优化

MySQL 是一个经过互联网大厂验证过的生产级别的成熟数据库,对于 Mysql 数据库本身的优化,一般是通过 Mysql 中的配置文件 my.cnf 来完成的,比如。

Mysql5.7 版本默认的最大连接数是 151 个,这个值可以在 my.cnf 中修改。

binlog 日志,默认是不开启

缓存池 bufferpoll 的默认大小配置等。

由于这些配置一般都和用户安装的硬件环境以及使用场景有关系,因此这些配置官方只会提供一个默认值,具体情况还得由使用者来修改。

关于配置项的修改,需要关注两个方面。

 配置的作用域,分为会话级别和全局

 是否支持热加载

因此,针对这两个点,我们需要注意的是:

 全局参数的设定对于已经存在的会话无法生效

 会话参数的设定随着会话的销毁而失效

 全局类的统一配置建议配置在默认配置文件中,否则重启服务会导致配置失效

SQL 优化

SQL 优化又能分为三步曲

 第一、慢 SQL 的定位和排查

我们可以通过慢查询日志和慢查询日志分析工具得到有问题的 SQL 列表。

 第二、执行计划分析

针对慢 SQL,我们可以使用关键字 explain 来查看当前 sql 的执行计划.可以重点关注type key rows filterd 等字段 ,从而定位该 SQL 执行慢的根本原因。再有的放矢的进 行优化

 第三、使用 show profile 工具

Show Profile 是 MySQL 提供的可以用来分析当前会话中,SQL 语句资源消耗情况的 工具,可用于 SQL 调优的测量。在当前会话中.默认情况下处于 show profile 是关闭状态,打开之后保存最近 15 次的运行结果

针对运行慢的 SQL,通过 profile 工具进行详细分析.可以得到 SQL 执行过程中所有的 资源开销情况. 如 IO 开销,CPU 开销,内存开销等.

以上就是我对 MySQL 性能优化的理解。

  1. TCP三次握手和四次挥手

\1. TCP 协议,是一种可靠的,基于字节流的,面向连接的传输层协议。

 可靠性体现在 TCP 协议通信双方的数据传输是稳定的,即便是在网络不好的情

况下,TCP 都能够保证数据传输到目标端,而这个可靠性是基于数据包确认机

制来实现的。

 TCP 通信双方的数据传输是通过字节流来实现传输的

 面向连接,是说数据传输之前,必须要建立一个连接,然后基于这个连接进行

数据传输

\2. 因为 TCP 是面向连接的协议,所以在进行数据通信之前,需要建立一个

可靠的连接,TCP 采用了三次握手的方式来实现连接的建立

三次握手

  1. 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
  2. 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

img

为什么要三次握手?

三次握手的意义在于确定双方都能够完成读写操作。

第一次握手,客户端发了个连接请求消息到服务端,服务端收到信息后知道自己与客户端是可以连接成功的,但此时客户端并不知道服务端是否已经接收到了它的请求,所以服务端接收到消息后的应答。客户端得到服务端的反馈后,才确定自己与服务端是可以连接上的,这就是第二次握手。而服务端发送出去的消息,要等客户端响应后,才能确定此次连接为有效连接。

四次挥手

在完成数据交互之后,如果选择关闭连接,以回收资源,则完成四次挥手来进行“和平分手”。

  1. 第一次挥手:主动关闭方发送第一个包,其中FIN标志位为1,发送顺序号seq为X。
  2. 第二次挥手:被动关闭方收到FIN包后发送第二个包,其中发送顺序号seq为Z,接收顺序号ack为X+1。
  3. 第三次挥手:被动关闭方再发送第三个包,其中FIN标志位为1,发送顺序号seq为Y,接收顺序号ack为X。
  4. 第四次挥手:主动关闭方发送第四个包,其中发送顺序号为X,接收顺序号为Y。至此,完成四次挥手。
为什么断开连接需要四次挥手?

三次握手是因为建立连接时,ACK和SYN可以放在一个报文里来发送。而关闭连接时,被动关闭方可能还需要发送一些数据后,再发送FIN报文表示同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。因此断开连接需要4次。

  1. 从输入URL到页面发生了什么

总体来说分为以下几个步骤:

  1. 在浏览器中输入指定网页的 URL。
  2. 浏览器通过 DNS 协议,获取域名对应的 IP 地址。
  3. 浏览器根据 IP 地址和端口号,向目标服务器发起一个 TCP 连接请求。
  4. 浏览器在 TCP 连接上,向服务器发送一个 HTTP 请求报文,请求获取网页的内容。
  5. 服务器收到 HTTP 请求报文后,处理请求,并返回 HTTP 响应报文给浏览器。
  6. 浏览器收到 HTTP 响应报文后,解析响应体中的 HTML 代码,渲染网页的结构和样式,同时根据 HTML 中的其他资源的 URL(如图片、CSS、JS 等),再次发起 HTTP 请求,获取这些资源的内容,直到网页完全加载显示。
  7. 浏览器在不需要和服务器通信时,可以主动关闭 TCP 连接,或者等待服务器的关闭请求。

img

上图有一个错误需要注意:是 OSPF 不是 OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议, 是由 Internet 工程任务组开发的路由选择协议

  1. 用户态和内核态的区别

根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:

  • 用户态(User Mode) : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。
  • 内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。

内核态相比用户态拥有更高的特权级别,因此能够执行更底层、更敏感的操作。不过,由于进入内核态需要付出较高的开销(需要进行一系列的上下文切换和权限检查),应该尽量减少进入内核态的次数,以提高系统的性能和稳定性。

为什么要有用户态和内核态?只有一个内核态不行么?

  • 在 CPU 的所有指令中,有一些指令是比较危险的比如内存分配、设置时钟、IO 处理等,如果所有的程序都能使用这些指令的话,会对系统的正常运行造成灾难性地影响。因此,我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 特权指令 。
  • 如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等,这将导致系统资源的竞争和冲突,从而影响系统性能和效率。并且,这样也会让系统的安全性降低,毕竟所有程序或进程都具有相同的特权级别和访问权限。

因此,同时具有用户态和内核态主要是为了保证计算机系统的安全性、稳定性和性能。

用户态和内核态是如何切换的?

用户态切换到内核态的 3 种方式:

  1. 系统调用(Trap):用户态进程 主动 要求切换到内核态的一种方式,主要是为了使用内核态才能做的事情比如读取磁盘资源。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
  2. 中断(Interrupt):当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
  3. 异常(Exception):当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

在系统的处理上,中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。

  1. 线程和进程的区别

  • 进程(Process) 是指计算机中正在运行的一个程序实例。举例:你打开的微信就是一个进程。
  • 线程(Thread) 也被称为轻量级进程,更加轻量。多个线程可以在同一个进程中同时执行,并且共享进程的资源比如内存空间、文件句柄、网络连接等。举例:你打开的微信里就有一个线程专门用来拉取别人发你的最新的消息
  • 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。
  • 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
  • 线程执行开销小,但不利于资源的管理和保护;而进程正相反。

有了进程为什么还需要线程?

  • 进程切换是一个开销很大的操作,线程切换的成本较低。
  • 线程更轻量,一个进程可以创建多个线程。
  • 多个线程可以并发处理不同的任务,更有效地利用了多处理器和多核计算机。而进程只能在一个时间干一件事,如果在执行过程中遇到阻塞问题比如 IO 阻塞就会挂起直到结果返回。
  • 同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核。
  1. HashSet和HashMap的区别和实现原理

HashSet 底层就是基于 HashMap 实现的

以下是他们的区别

img

HashMap 和 TreeMap 区别

TreeMap 和HashMap 都继承自AbstractMap ,但是需要注意的是TreeMap它还实现了NavigableMap接口和SortedMap 接口。

相比于HashMap来说, TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。

  1. Sychoronized和Volatile的区别和解决了什么

synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!

  • volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性
  1. synchronized的锁升级过程

在 jdk1.6 版本中,synchronized 增加了锁升级的机制,来平衡数据安全性和性能。

简单来说,就是线程去访问 synchronized 同步代码块的时候,synchronized 根

据线程竞争情况,会先尝试在不加重量级锁的情况下去保证线程安全性。所以引入了偏向锁和轻量级锁的机制。 偏向锁,就是直接把当前锁偏向于某个线程,简单来说就是通过 CAS 修改偏向锁标记, 这种锁适合同一个线程多次去申请同一个锁资源并且没有其他线程竞争的场景。 轻量级锁也可以称为自旋锁,基于自适应自旋的机制,通过多次自旋重试去竞争锁。自旋锁优点在于它避免避免了用户态到内核态的切换带来的性能开销。

Synchronized 引入了锁升级的机制之后,如果有线程去竞争锁:

首先,synchronized 会尝试使用偏向锁的方式去竞争锁资源,如果能够竞争到偏

向锁,表示加锁成功直接返回。如果竞争锁失败,说明当前锁已经偏向了其他线程。

需要将锁升级到轻量级锁,在轻量级锁状态下,竞争锁的线程根据自适应自旋次数

去尝试抢占锁资源,如果在轻量级锁状态下还是没有竞争到锁,

就只能升级到重量级锁,在重量级锁状态下,没有竞争到锁的线程就会被阻塞,线

程状态是 Blocked。 处于锁等待状态的线程需要等待获得锁的线程来触发唤醒

总的来说, Synchronized 的锁升级的设计思想,在我看来本质上是一种性能和安全性的平衡,也就是如何在不加锁的情况下能够保证线程安全性。

  1. Synchronized是公平锁吗,ReetrantLock是怎么实现公平锁

不是

ReentrantLock 比 synchronized 增加了一些高级功能

相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:

等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来指定是否是公平的。

可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

  1. Java中对象的创建过程

类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程

分配内存

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

执行init方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

  1. 垃圾回收的过程

  2. CHAR和VARCHAR有什么区别

第一个,char 是一个固定长度的字符串,Varchar 是一个可变长度的字符串

假设声明一个 char(10)的长度,如果存储字符串“abc”,虽然实际字符长度只有 3,

但是 char 还是会占 10 个字节长度。

同样,如果用 varchar 存储,那它只会使用 3 个字符的实际长度来存储。

 第二个,存储的效率不同,char 类型每次修改以后存储空间的长度不变,所以效率更高 varchar 每次修改数据都需要更新存储空间长度,效率较低

 第三个,存储空间不同,char 不管实际数据大小,存储空间是固定的,而 varchar

存储空间等于实际数据长度, 所以 varchar 实际存储空间的使用要比 char 更小

基于他们特性的分析,可以得出一个基本的结论:

 char 适合存储比较短的且是固定长度的字符串

 varchar 适合存储可变长度的字符串

  1. 介绍一下索引

索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。

索引的作用就相当于书的目录。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam,都使用了 B+树作为索引结构

索引的优缺点

优点:

  • 使用索引可以大大加快数据的检索速度(大大减少检索的数据量), 减少 IO 次数,这也是创建索引的最主要的原因。
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

缺点:

  • 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
  • 索引需要使用物理文件存储,也会耗费一定空间。

但是,使用索引一定能提高查询性能吗?

大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。

14介绍一下日志

MySQL 日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志 binlog(归档日志)和事务日志 redo log(重做日志)和 undo log(回滚日志)

具体

MySQL三大日志(binlog、redo log和undo log)详解 | JavaGuide

  1. redolog如何保证持久性

1.记录修改操作:当数据发生修改时,不是直接修改数据,而是在Redo log中先记录本次操作。这个操作是顺序写入的,速度非常快。

2.数据修改:在Redo log中记录操作后,再修改缓冲区中的数据。这一步是实际的数据修改

3.日志刷盘:在事务提交时或者提交前,通过调用fsync接口将Redo log写入磁盘。这一步是为了确保在数据库系统发生故障时,已经记录的修改操作不会丢失。

4.故障恢复:如果数据库系统发生故障并宕机,那么在重启时,系统会读取Redo log中的数据,根据这些日志对数据库进行恢复。由于Redo log是预写日志,它记录了所有已经提交的事务的修改操作,因此可以用来恢复数据库到一致的状态。通过这种方式,Redo log保证了即使在数据库系统发生故障并宕机的情况下,已经提交的事务的修改也不会丢失,从而满足了持久性的要求。同时,由于Redo log的顺序写入和快速恢复的特性,它也在一定程度上提高了数据库系统的性能。

  1. 能不能只用binLog不用redolog

不能只用binlog而不用redo log。虽然binlog和redo log都是用于保证数据库持久性的重要机制,但它各自的作用和用途是不同的。

binlog(二进制日志)主要用于记录数据库的所有更改,包括数据修改和表结构变更等。它可以用于数据恢复和主从复制等场景。然而,binlog并不具备崩渍恢复的能力,也就是说,它不能单独用于在系统崩溃后恢复数据。

而redolog(重做日志)则主要用于保证事务的持久性。在数据库事务提交前,相关的修改操作会先写入redo log中,并在系统崩渍后利用这些日志来恢复数据。这样可以确保即使系统发生故障,已经提交的事务的修改也不会丢失。

因此,虽然binlog和redo log都是数据库持久性的重要保障,但它们各自的作用和用途是不同的。在实际应用中,通常会将它们结合使用,以确保数据库的特久性和可靠性。如果只使用binlog而不使用redo log,那么在系统崩渍后可能无法恢复已经提交的事务的修改,从而导致数据丢失。

  1. ACID特性

· 原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

· 一致性(Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;

· 隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

· 持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

  1. 四种事务隔离级别

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交) :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读) :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化) :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

img

MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)

从上面对 SQL 标准定义了四个隔离级别的介绍可以看出,标准的 SQL 隔离级别定义里,REPEATABLE-READ(可重复读)是不可以防止幻读的。

但是!InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况:

  • 快照读:由 MVCC 机制来保证不出现幻读。
  • 当前读:使用 Next-Key Lock 进行加锁来保证不出现幻读,Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的结合,行锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED ,但是你要知道的是 InnoDB 存储引擎默认使用 REPEATABLE-READ 并不会有任何性能损失。

InnoDB 存储引擎在分布式事务的情况下一般会用到 SERIALIZABLE 隔离级别。

  1. update语句的具体执行流程

1.解析(Parsing):

客户端发送UPDATE语句到服务器。

服务器对SQL语句进行语法解析,生成解析树。

进行语义检查,例如检查表和列名是否存在,权限是否足够等。

2.预处理(Preprocessing):

生成执行计划(Execution Plan)。优化器会决定如何高效地执行这个UPDATE语句,例如选择哪个索引,如何进行数据查找等。确定影响的行数,这会影响到后续操作(如锁的数量)。

3.锁定(Locking):

根据优化器生成的执行计划,确定需要锁定哪些行或表。

如果是InnoDB存储擎,它会在事务开始时使用行锁或表锁来确保数据的一致性。

4.执行(Execution):

根据解析和预处理的结果,开始实际的数据更新操作。

遍历每一行(或者通过索引直接定位到某些行),应用UPDATE语句中的修改。

对于每一行,MySQL会先检查该行是否满足UPDATE语句的WHERE条件,如果满足则进行更新。

5.写入redo log(如果是InnoDB):

在数据实际更新到磁盘之前,InnoDB会先将这个更新操作记泉到redo log中。

redo log是循环写的,当达到一定大小时会从头开始写。

6.更新数据页(如果是InnoDB):

将数据实际更新到内存中的缓冲池中。

如果这个修改导致数据页变得不整洁(比如半满或者混合了新旧版本的数据),可能会触发页合并或者页分裂的操作。

7.写入binlog(如果是配置了的话):

将这次UPDATE操作的信息写入到binlog中。

binlog是MySQL用于复制和恢复的数据日志。

8.提交事务(Commit):

如果[PDATE语句在事务中执行,那么在所有的更新操作都成功之后,事务会被提交。

在事务提交时,M小ySQL会将redo log和binlog都刷新到磁盘上,确保数据的持久性。

如果使用了两阶段提交协议,在提交阶段还会进行额砂外的检查和日志写入。

9.返回结果:

客户端接收更新操作的结果,包括更新的行数等信息。

10.清理(Cleanup):

释放相关的锁和资源。

  1. 如果有一个字段是status值为0或者1,适合建索引吗

假设表中有一千万条记录,某个状态为0的记录总数大概有100条,那么你想查询状态为0的记录时,有没有索引影响非常大,而查询状态为1的记录,则索引基本无用。如果两种状态的记录数相差无几的话,索引也基本无用

假设表中有一千万条记录,某个状态为0的记录总数大概会有100条,就在这个状态列建立索引其他两种情况不需要建立索引,毕竟索引空间消耗不小的

  1. redis的zset底层是怎么实现的

因为设计者考虑到 Redis 数据存放于内存,为了节约宝贵的内存空间在

  1. ZSet 保存的键值对数量少于 128 个;
  2. 每个元素的长度小于 64 字节。

会使用 ziplist一旦有序集合中的某个元素超出这两个其中的一个阈值它就会转为 skiplist(实际是 dict+skiplist,还会借用字典来提高获取指定元素的效率)。

某厂java后端实习一面

  1. 两个对象的HashCode相同会怎么样

hashCode() 和 equals()都是用于比较两个对象是否相等。

那为什么 JDK 还要同时提供这两个方法呢?

这是因为在一些容器(比如 HashMap、HashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HashSet的过程)!

我们在前面也提到了添加元素进HashSet的过程,如果 HashSet 在对比的时候,同样的 hashCode 有多个对象,它会继续使用 equals() 来判断是否真的相同。也就是说 hashCode 帮助我们大大缩小了查找成本。

那为什么不只提供 hashCode() 方法呢?

这是因为两个对象的hashCode 值相等并不代表两个对象就相等。

那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?

因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。

总结下来就是:

  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。
  1. 线程池的参数及工作原理

ThreadPoolExecutor 3 个最重要的参数:

  • corePoolSize : 核心线程数量,任务队列未达到队列容量时,最大可以同时运行的线程数量。
  • maximumPoolSize : 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • workQueue(工作队列): 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数 :

  • keepAliveTime(空闲线程存活时间):线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁。
  • unit : keepAliveTime 参数的时间单位。
  • threadFactory(线程工厂) :executor 创建新线程的时候会用到。
  • handler :拒绝策略
  1. AQS有了解吗,说一下加锁的过程

AQS 是多线程同步器,它是 J.U.C 包中多个组件的底层实现,如 Lock、

CountDownLatch、Semaphore 等都用到了 AQS.

从本质上来说,AQS 提供了两种锁机制,分别是排它锁,和 共享锁。

排它锁,就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资

源,也就是多个线程中只能有一个线程获得锁资源,比如 Lock 中的 ReentrantLock 重

入锁实现就是用到了 AQS 中的排它锁功能。

共享锁也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如

CountDownLatch 和 Semaphore 都是用到了 AQS 中的共享锁功能。

AQS 详解 | JavaGuide

  1. 聚簇索引和非聚簇索引的区别

聚簇索引(Clustered Index)即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。

优点:

  • 查询速度非常快:聚簇索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。相比于非聚簇索引, 聚簇索引少了一次读取数据的 IO 操作。
  • 对排序查找和范围查找优化:聚簇索引对于主键的排序查找和范围查找速度非常快。

缺点:

  • 依赖于有序的数据:因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
  • 更新代价大:如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚簇索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的

非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。

非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。

优点:

更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的。

缺点:

  • 依赖于有序的数据:跟聚簇索引一样,非聚簇索引也依赖于有序的数据
  • 可能会二次查询(回表):这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。

携程后端一面

1.ArratList和LinkList的区别

  • 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  • 底层数据结构: ArrayList 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
  • 插入和删除是否受元素位置的影响:
  • ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element)),时间复杂度就为 O(n)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
  • LinkedList 采用链表存储,所以在头尾插入或者删除元素不受元素位置的影响(add(E e)、addFirst(E e)、addLast(E e)、removeFirst()、 removeLast()),时间复杂度为 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element),remove(Object o),remove(int index)), 时间复杂度为 O(n) ,因为需要先移动到指定位置再插入和删除。
  • · 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList(实现了 RandomAccess 接口) 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  • · 内存空间占用: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
  1. JVM双亲委派机制

首先,我简单说一下类的加载机制,就是我们自己写的 java 源文件到最终运行,必须要经过编译和类加载两个阶段。

编译的过程就是把.java 文件编译成.class 文件。

类加载的过程,就是把 class 文件装载到 JVM 内存中,装载完成以后就会得到一个 Class 对象,我们就可以使用 new 关键字来实例化这个对象。

而类的加载过程,需要涉及到类加载器。

JVM 在运行的时候,会产生 3 个类加载器,这三个类加载器组成了一个层级关系

每个类加载器分别去加载不同作用范围的 jar 包,比如

  • Bootstrap ClassLoader,主要是负责 Java 核心类库的加载,也就是 %{JDK_HOME}\lib 下的 rt.jar、resources.jar 等
  • Extension ClassLoader,主要负责%{JDK_HOME}\lib\ext 目录下的 jar 包和 class 文件
  • Application ClassLoader,主要负责当前应用里面的 classpath 下的所有 jar 包和类文件

除了系统自己提供的类加载器以外,还可以通过 ClassLoader 类实现自定义加载器,去满足一些特殊场景的需求

所谓的父委托模型,就是按照类加载器的层级关系,逐层进行委派。

比如当需要加载一个 class 文件的时候,首先会把这个 class 的查询和加载委派给父加 载器去执行,如果父加载器都无法加载,再尝试自己来加载这个 class。

这样设计的好处,我认为有几个。

\1. 安全性,因为这种层级关系实际上代表的是一种优先级,也就是所有的类的加载,

优先给 Bootstrap ClassLoader。那对于核心类库中的类,就没办法去破坏,比如

自己写一个 java.lang.String,最终还是会交给启动类加载器。再加上每个类加载

器的作用范围,那么自己写的 java.lang.String 就没办法去覆盖类库中类。

\2. 我认为这种层级关系的设计,可以避免重复加载导致程序混乱的问题,因为如果父加载器已经加载过了,那么子类就没必要去加载了。

  1. 创建线程的方式有哪几种

一般来说,创建线程有很多种方式,例如继承Thread类、实现Runnable接口、实现Callable接口、使用线程池、使用CompletableFuture类等等。

不过,这些方式其实并没有真正创建出线程。准确点来说,这些都属于是在 Java 代码中使用多线程的方法。严格来说,Java 就只有一种方式可以创建线程,那就是通过new Thread().start()创建。不管是哪种方式,最终还是依赖于new Thread().start()。

大家都说Java有三种创建线程的方式!并发编程中的惊天骗局! (qq.com)

  1. 了解线程池吗

什么是线程池?

顾名思义,线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。

为什么要用线程池?

池化技术想必大家已经屡见不鲜了,线程池、数据库连接池、HTTP 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

线程池提供了一种限制和管理资源(包括执行一个任务)的方式。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。

这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

如何创建线程池?

方式一:通过ThreadPoolExecutor构造函数来创建(推荐)。

方式二:通过 Executor 框架的工具类 Executors 来创建。

可以看出,通过Executors工具类可以创建多种类型的线程池,包括:

FixedThreadPool:固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。

SingleThreadExecutor: 只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。

CachedThreadPool: 可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。

ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池。

  1. Session和cookie的区别

Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。

Cookie 一般用来保存用户信息 比如 ① 我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;② 一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③ 登录一次网站后访问网站其他页面不需要重新登录。Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。

Cookie 存储在客户端中,而 Session 存储在服务器上,相对来说 Session 安全性更高。如果使用Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。

  1. 解释一下SpringIOC和AOP的原理

IOC & AOP详解(快速搞懂) | JavaGuide

淘天java暑期一面

  1. 封装,继承,多态

封装

封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。

继承

不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。(以后介绍)。

多态

多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

多态的特点:

  • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
  1. 抽象类和接口

接口和抽象类有什么共同点和区别?

共同点:

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。

区别:

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
  1. 垃圾回收

JVM垃圾回收详解(重点) | JavaGuide

  1. java的引用计数

死亡对象判断方法

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

引用计数法

给对象中添加一个引用计数器:

  • 每当有一个地方引用它,计数器就加 1;
  • 当引用失效,计数器就减 1;
  • 任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。

可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

哪些对象可以作为 GC Roots 呢?

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象
  • JNI(Java Native Interface)引用的对象

对象可以被回收,就代表一定会被回收吗?

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。

被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

  1. 内存泄漏

ThreadLocal 内存泄露问题是怎么导致的?

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法

  1. 快速排序

十大经典排序算法总结 | JavaGuide

  1. 内存分页

4.1 为什么要有虚拟内存? | 小林coding (xiaolincoding.com)

  1. 泛型

什么是泛型?有什么作用?

Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。

编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayListpersons = new ArrayList() 这行代码就指明了该 ArrayList 对象只能传入 Person 对象,如果传入其他类型的对象就会报错。

泛型的使用方式有哪几种?

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。

  1. 并发和并行

    • 并发:两个及两个以上的作业在同一 时间段 内执行。
    • 并行:两个及两个以上的作业在同一 时刻 执行。

同步和异步

  • 同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
  • 异步:调用在发出之后,不用等待返回结果,该调用直接返回。
  1. 操作系统的底层逻辑

3.1 Linux 内核 vs Windows 内核 | 小林coding (xiaolincoding.com)

  1. linux软链接和硬链接

有时候我们希望给某个文件取个别名,那么在 Linux 中可以通过硬链接(Hard Link) 和软链接(Symbolic Link) 的方式来实现,它们都是比较特殊的文件,但是实现方式也是不相同的。

硬链接是多个目录项中的「索引节点」指向一个文件,也就是指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。

软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。

7.1 文件系统全家桶 | 小林coding (xiaolincoding.com)

12.git的指令

Git核心概念总结 | JavaGuide

13.MVCC

InnoDB存储引擎对MVCC的实现 | JavaGuide

飞书一面

  1. 抽象类和接口的区别

  2. 抽象类可以被实例化吗

  3. 聊一下双亲委派机制

  4. 有几种类型的类加载器,都具体是干什么的

JVM 中内置了三个重要的 ClassLoader:

  1. BootstrapClassLoader(启动类加载器):最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库( %JAVA_HOME%/lib目录下的 rt.jar、resources.jar、charsets.jar等 jar 包和类)以及被 -Xbootclasspath参数指定的路径下的所有类。
  2. ExtensionClassLoader(扩展类加载器):主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。
  3. AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。

类加载器详解(重点) | JavaGuide

5.java中有哪些类型的锁

Java中常见的各种锁(非常全)_java滑块锁-CSDN博客

Java进阶(锁)——锁分类总结,Java中常用的锁的介绍_java锁-CSDN博客

6.synchronized的偏向锁和自旋锁是怎么实现的

Synchronized的原理及自旋锁,偏向锁,轻量级锁,重量级锁的区别_synchronize原理, 旋锁等 系列锁-CSDN博客

Java并发常见面试题总结(中) | JavaGuide

  1. 偏向锁存线程的消息存在哪

Java锁synchronized关键字学习系列之偏向锁_偏向锁如何判断是否同一线程-CSDN博客

img

  1. java对象头里面有什么

Java的对象头和对象组成详解-CSDN博客

  1. 计算机网络的分层模型说一下,每个层有什么协议

计算机网络常见面试题总结(上) | JavaGuide

  1. tcp如何实现可靠传输

  2. 流量控制则被么实现TCP 传输可靠性保障(传输层) | JavaGuide

  3. 什么是SYN攻击

网络攻击常见手段总结 | JavaGuide

  1. http常见的头部,头部里面有哪些键,content-length作用

面试官:说说 HTTP 常见的请求头有哪些? 作用? | web前端面试 - 面试官系列 (vue3js.cn)

  1. utf-8和unicode什么区别

字符集详解 | JavaGuide

  1. 进程包含什么

操作系统常见面试题总结(上) | JavaGuide

  1. redis中有哪些数据结构

  2. zset是如何实现有序的

  3. 跳表怎么维护平衡

Redis 数据结构 | 小林coding (xiaolincoding.com)

  1. redis新节点加入集群会发生什么

  2. 从节点怎么跟主节点同步数据

为什么要有哨兵? | 小林coding (xiaolincoding.com)

  1. 写操作日志记录什么

如何使用Redis来记录日志?_redis log_recent-CSDN博客

鹅厂CSIG

  1. java的hashMap底层数据结构

我从三个方面讲一下它

1.HashMap 中的关键属性—看-HashMap 的源码片段,可以知道它的几个属性

第一个属性 loadFactor,它是负载因子,默认值是 0.75,表示扩容前。

第二个属性 threshold 它是记录 HashMap 所能容纳的键值对的临界值,它的计算规 则是负载因子乘以数组长度。

第三个属性 size,它用来记录 HashMap 实际存在的键值对的数量。

第四个属性 modCount,它用来记录 HashMap 内部结构发生变化的次数。

第五个是常量属性 DEFAULT_INITIAL_CAPACITY ,它规定 的默认容量是 16。

2、HashMap 的存储结构

HashMap 采用的是K-V 的存储结构。HashMap 的数组部分称为 Hash 桶,数组元素保存 在一个叫做 table 的属性中。jdk1.8以后当链表长度大于等于 8 时,链表数据将会以红黑树的形式进行存储,当长度降到 6 时,又会转成链表形式存储。

每个 Node 节点,保存了用来定位数组索引位置的 hash 值、Key、Value 和链表指向 的下一个 Node 节点。而 Node 类是 HashMap 的内部类,它实现了 Map.Entry 接口, 它的本质其实可以简单的理解成就是一个键值对。

  1. HashMap 的工作原理

首先,通过hash算法计算出key的hashcode值,然后根据数组长度取模,得到在数组中的位置。

如果该位置上没有元素,则直接将key-value存储在该位置上。

如果该位置上已经存在元素,则需要进行链表或红黑树的操作。

如果链表长度小于8,则采用链表存储,将新元素插入到链表尾部。

如果链表长度大于等于8,则将链表转化为红黑树进行存储。

当数组长度达到阈值时(默认为0.75),需要进行扩容操作,将数组长度扩大为原来的两倍,并将原有元素重新分布到新数组中。

当数组中的元素数量小于阈值的0.25时,会进行缩容操作,将数组长度缩小为原来的一半。

  1. concurrentHashMap是怎么保证线程安全的

我们知道 HashMap 不是线程安全的,在并发场景下如果要保证一种可行的方式是使用 Collections.synchronizedMap() 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。

所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。

Java7 中 ConcurrentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。

Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。ConcurrentHashMap 源码分析 | JavaGuide

  1. java锁框架介绍一下 AQS

AQS 是多线程同步器,它是 J.U.C 包中多个组件的底层实现,如 Lock、

CountDownLatch、Semaphore 等都用到了 AQS.

从本质上来说,AQS 提供了两种锁机制,分别是排它锁,和 共享锁。

排它锁,就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资

源,也就是多个线程中只能有一个线程获得锁资源,比如 Lock 中的 ReentrantLock 重

入锁实现就是用到了 AQS 中的排它锁功能。

共享锁也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如

CountDownLatch 和 Semaphore 都是用到了 AQS 中的共享锁功能。

AQS 详解 | JavaGuide

  1. java垃圾回收是怎么做的

JVM垃圾回收详解(重点) | JavaGuide

  1. Spring的核心思想IOC和AOP

IoC (Inversion of Control )即控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。

· 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来

· 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。

IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?

  1. 对象之间的耦合度或者说依赖程度降低;
  2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

什么是 AOP?

AOP(Aspect Oriented Programming)即面向切面编程,AOP 是 OOP(面向对象编程)的一种延续,二者互补,并不对立。

AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。OOP 的目的是将业务逻辑按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化(也能实现代码的复用),提高代码的可读性和可维护性

详见

IoC & AOP详解(快速搞懂) | JavaGuide

讯飞消费者java日常实习

  1. jvm内存划分

img

Java堆

Java堆是java虚拟机所管理的内存中最大的一块,是被所有线程都共享的内存区域。存在的唯一目的就是存放对象实例,几乎所有的对象实例都在这里进行分配内存。不过目前随着技术的不断发展,也并不是所有的对象实例都在堆中分配内存,可能也存在栈上分配。由于所占空间大,又存放各种实例对象,因此java虚拟机的垃圾回收机制主要管理的就是此区域,详细的垃圾回收方法以后会提到。JVM规范中规定堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。并且可以通过-Xmx和-Xms来扩展堆的内存大小,如果在堆中没有足够的内存为实例分配,并且堆也无法在扩展时,就会报OutOfMemoryError异常。

方法区

跟Java堆一样,方法区是各个线程共享的内存区域,此区域是用来存储类的信息(类的名称、字段信息、方法信息)、静态变量、常量以及编译器编译后的代码。JVM规范中并不区分方法区和堆,只把方法区描述为堆的逻辑部分,但是它却有一个别名叫做非堆(Non-Heap),目的就是与Java堆区分开。根据垃圾回收机制中分代回收的思想,如果在HotSpot虚拟机上开发,可以把方法区称为“永久代”(只是可以这么理解,但实质是不一样的),垃圾回收机制在Java堆中划分一个部分称为永久代,用此区域来实现方法区,这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存,而不必为方法区开发专门的内存管理器。

运行时常量池

运行时常量池是方法区的一个部分,class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容会在类加载后进入方法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

程序计数器

虽然在上图中程序计数器的面积很大,但实际上它是一块较小的内存空间,可以看做当前线程所执行字节码的行号指示器。字节码解释器在工作中时下一步该干啥、到哪了,就是通过它来确定的。大家都知道在多线程的情况下,CPU在执行线程时是通过轮流切换线程实现的,也就是说一个CPU处理器(假设是单核)都只会执行一条线程中的指令,因此为了线程切换后能恢复到正确的执行位置,每个线程都要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。很明显,程序计数器就是线程私有的。如果线程正在执行的是一个java方法,程序计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的Native方法,程序计数器记录的值为空(Undefined),此内存区域是java中唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

我们经常会把java内存粗糙的分为两个部分,堆和栈,Java虚拟机栈就是栈这一部分,或者说是虚拟机栈中局部变量表部分。跟程序计数器一样,虚拟机栈也是线程私有的,它的生命周期跟线程相同。每个方法在执行的同时都会创建一个栈帧(Stack Frame),每个栈帧对应一个被调用的方法,栈帧中用于存储局部变量表、操作数栈、动态链表、方法出口等信息。每一个方法从开始执行到结束就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表:顾名思义,他就是用来存储方法中的局部变量(包括在方法中生命的非静态变量以及函数形参),对于基本数据类型,直接存值,对于引用类型的变量,存储指向该对象的引用。由于它只存放基本数据类型的变量、引用类型的地址和返回值的地址,这些类型所需空间大小已知且固定,所以当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全可以确定的,在方法运行期间也不会改变局部变量表的大小。

指向运行常量池的引用:在方法执行过程中难免会使用到类中定义的常量,因此栈帧中要存放一个指向运行时常量池的引用。

方法返回地址:当一个方法执行结束后,要返回到之前调用它的地方,因此在栈帧中需要保存一个方法返回地址。

本地方法栈

本地方法栈与虚拟机栈的功能非常的相似,区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机执行Native方法服务。有的虚拟机并不会区分本地方法栈和虚拟机栈,比如Sun HotSpot虚拟机直接将两个合二为一。

Java内存区域详解(重点) | JavaGuide

  1. new创建的对象一定在堆吗,局部变量是基本类型创建在哪,如果基本类型是成员变量呢

Java中的对象不一定是在堆上分配的,因为JVM通过逃逸分析,能够分析出一个新对象的使用范围,并以此确定是否要将这个对象分配到堆上。

逃逸分析的概念

先以官方的形式来说下什么是逃逸分析。逃逸分析就是:一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针。

在JVM的即时编译语境下,逃逸分析将判断新建的对象是否逃逸。即时编译判断对象是否逃逸的依据:一种是对象是否被存入堆中(静态字段或者堆中对象的实例字段),另一种就是对象是否被传入未知代码。

直接说这些概念,确实有点晕啊,那我们就来两个示例。

一种典型的对象逃逸就是:对象被复制给成员变量或者静态变量,可能被外部使用,此时变量就发生了逃逸。

在ObjectEscape类中,存在一个成员变量user,我们在init()方法中,创建了一个User类的对象,并将其赋值给成员变量user。此时,对象被复制给了成员变量,可能被外部使用,此时的变量就发生了逃逸。

另一种典型的场景就是:对象通过return语句返回。如果对象通过return语句返回了,此时的程序并不能确定这个对象后续会不会被使用,外部的线程可以访问到这个变量,此时对象也发生了逃逸。

逃逸分析的优点

逃逸分析的优点总体上来说可以分为三个:对象可能分配在栈上、分离对象或标量替换、消除同步锁。。

1对象可能分配在栈上

JVM通过逃逸分析,分析出新对象的使用范围,就可能将对象在栈上进行分配。栈分配可以快速地在栈帧上创建和销毁对象,不用再将对象分配到堆空间,可以有效地减少 JVM 垃圾回收的压力。

2分离对象或标量替换

当JVM通过逃逸分析,确定要将对象分配到栈上时,即时编译可以将对象打散,将对象替换为一个个很小的局部变量,我们将这个打散的过程叫做标量替换。将对象替换为一个个局部变量后,就可以非常方便的在栈上进行分配了。

3同步锁消除

如果JVM通过逃逸分析,发现一个对象只能从一个线程被访问到,则访问这个对象时,可以不加同步锁。如果程序中使用了synchronized锁,则JVM会将synchronized锁消除。

这里,需要注意的是:这种情况针对的是synchronized锁,而对于Lock锁,则JVM并不能消除。

要开启同步消除,需要加上 -XX:+EliminateLocks 参数。因为这个参数依赖逃逸分析,所以同时要打开 -XX:+DoEscapeAnalysis 选项。

所以,并不是所有的对象和数组,都是在堆上进行分配的,由于即时编译的存在,如果JVM发现某些对象没有逃逸出方法,就很有可能被优化成在栈上分配。

局部变量是基本类型:

当我们在方法中声明一个基本类型的局部变量时,这个变量会存储在java虚拟机的栈内存中。具体来说,这个局部变量会存储在方法区的栈帧(Stack Frame)的局部变量表(Local VariableTble)中。每个方法被调用时,JVM都会为该方法创建一个新的栈帧,并在该栈帧的局部变量表中为方法的所有局部变量分配空间。当方法执行完毕时,这个栈帧就会被销毁,局部变量也随,之被销毁。

基本类型是成员变量:

成员变量(无论是静态的还是非静态的)都是类的属性,并且与类相关联。当成员变量是基本类型时,它们会作为对象实例的一部分存储在堆内存中。每个对象实例在堆上都有一个独立的存储空间,其中包含该刻对象所有成员变量的值。静态成员变量(也称为类变量)则存储在Java的方法区中。

  1. jvm堆内存详细说一下,为什么要这么划分,用的什么垃圾回收算法

Java内存区域详解(重点) | JavaGuide

JVM垃圾回收详解(重点) | JavaGuide

  1. 什么时候会发生full gc

\1. 调用 System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

\2. 未指定老年代和新生代大小,堆伸缩时会产生fullgc,所以一定要配置-Xmx、-Xms

\3. 老年代空间不足

老年代空间不足的常见场景比如大对象、大数组直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。

除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。

还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

在执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space

\4. JDK 1.7 及以前的(永久代)空间满

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。

如果经过 Full GC 仍然回收不了,那么虚拟机会抛出java.lang.OutOfMemoryError PermGen space

为避免以上原因引起的 Full GC,可采用的方法为增大Perm Gen或转为使用 CMS GC。

\5. 空间分配担保失败

空间担保,下面两种情况是空间担保失败:

1、每次晋升的对象的平均大小 > 老年代剩余空间

2、Minor GC后存活的对象超过了老年代剩余空间

注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当出现这两种状况的时候就有可能会触发Full GC。

promotion failed 是在进行 Minor GC时候,survivor space空间放不下只能晋升老年代,而此时老年代也空间不足时发生的。

concurrent mode failure 是在进行CMS GC过程,此时有对象要放入老年代而空间不足造成的,这种情况下会退化使用Serial Old收集器变成单线程的,此时是相当的慢的。

  1. full gc对程序的影响

1.应用程序停顿:在Full GC进行期间,JVM(Jva虚拟机)会暂停所有的应用程序线程,等待垃圾回收完成之后再恢复线程的执行。这意味着在Full GC期间,应用程序无法执行任何操作,会导致应用程序出现长时间的停顿和卡顿,影响用户体验。

2.系统性能损耗:由于Full GC需要遍历整个堆内存,所以它的耗时较长,会消耗大量的系统资源,导致系统性能下降。如果Full GC发生的频率过高或回收的对象数量过大,会导致系统性能损耗更加严重。

3.内存不足:如果Full GC无法回收足够的内存,会导致OutOfMemoryError(OOM)错误,从而导致系统崩渍。这种情况下,通常需要增加堆内存的大小或优化代码实现,以减少内存的使用。

  1. 怎么解决full GC

1.增大堆内存:通过增加Jva堆内存的大小,可以减少Full GC的频率,从而降低系统卡死的风险。可以使用-x和-s参数来调整VM的最大和初始堆内存大小。

2.检查内存泄漏:定期检查应用程序,确保没有潜在的内存泄漏问题。内存泄漏会导致堆内存不断增长,最终触发Full GC。可以使用工具如jconsole、.jvisualvm或MAT(Memory Analyzer Tool)来分析堆内存和查找内存泄世漏。

3.优化大对象的处理:对于频繁创建的大对象,可以考虑使用对象池或重用对象,以减少Full GC的负担。此外,也可以尝试调整JVM参数,如新生代大小、Edn区和Survivor区的比例等,以适应大对象的处理

4.优化Finalizer的使用:尽量避免过度使用Finalizer,确保Finalizer方法的执行时间尽量短暂。Finalizer的过度使用可能会导致对象无法及时被回收,从而增加Full GC的频率

5.调整并发线程数:根据应用程序的负载和硬件环境,调整垃圾回收器的并发线程数,以平衡垃圾回收和应用程序的执行。过多的并发线程可能会导致系统资源竞争,影响性能。

6.优化代码实现:通过优化代码实现,减少不必要的对象创建和销毁,降低内存的使用。例如,使用缓存技术来减少数据库查询次数,避免频繁创建和销毁对象等。

7.选择合适的垃圾回收器:根据应用程序的特点和性能需求,选择合适的垃圾回收器。例如,对于延迟敏感的应用,可以选择G1垃圾回收器;对于内存占用较多的应用,可以选择CMS垃圾回收器等。

综上所述,解决Full GC的问题需要综合考虑多个方面,包括调整JVM参数、优化代码实现、检查和修复内存泄漏等。通过综合运用这些解决方法,可以降低Fu川GC的频率和影响,提高系统的稳定性和性能。

  1. 线上系统发生了full gc该如何快速解决

1.快速恢复系统:

首先,需要尽快恢复系统的正常使用。这可能包括使用机器扩容、服务重启、接口限流等手段来维持服务的正常运行,以减轻Full GC带来的压力,并给问题定位争取时间。

2.查看监控和日志:

使用监控工具查看系统性能指标,如CPU、内存、GC次数等,了解Full GC发生的频率和严重程度。

查看GC日志,分析Full GC的原因。GC日志可以提供关于每次GC的详细信息,如GC的类

型、GC的时间、回收的内存量等。

3.定位问题原因:

通过分析监控数据和GC日志,定位导致Full GC的具体原因。可能的原因包括内存泄漏、不合理的内存分配、过多的临时对象、不合理的对象引用等。

使用JVM调优工具,如jmap、jstack、jvisualvm等,进一步分析内存使用情况、线程堆栈信息等,帮助定位问题。

4.紧急措施:

如果FullGC导致系统无法正常运行,可以考虑采取紧急措施,如回滚到上一个稳定版本,暂时缓解问题。

如果问题是由新上线的代码导致的,需要尽快回滚到上一个版本,并修复问题后再重新上线。

得帆 Java一面

  1. Java集合

Java集合常见面试题总结(上) | JavaGuide

  1. 创建线程的方式

一般来说,创建线程有很多种方式,例如继承Thread类、实现Runnable接口、实现Callable接口、使用线程池、使用CompletableFuture类等等。

不过,这些方式其实并没有真正创建出线程。准确点来说,这些都属于是在 Java 代码中使用多线程的方法。

严格来说,Java 就只有一种方式可以创建线程,那就是通过new Thread().start()创建。不管是哪种方式,最终还是依赖于new Thread().start()。

大家都说Java有三种创建线程的方式!并发编程中的惊天骗局! (qq.com)

  1. 对spring的理解

4.springboot与spring的区别

Spring常见面试题总结 | JavaGuide

5如何解决循环依赖

Spring常见面试题总结 | JavaGuide

  1. 对mybais的理解

MyBatis常见面试题总结 | JavaGuide

  1. 实现一个抢红包的案例(后面统一总结学习)

  2. 缓存三兄弟

Redis常见面试题总结(下) | JavaGuide

  1. 设计模式(看视频)

10.接口响应慢的处理思路

1.寻找性能瓶颈

首先,我们需要找出导致接口响应慢的原因。可能的原因包括:

  • 数据库查询慢:接口中涉及到数据库查询操作,可能是查询语句不够优化,或者数据库索引不够合理。
  • 外部依赖调用慢:接口调用了其他服务或者外部资源,可能是调用的服务响应慢或者网络延迟高。
  • 高并发压力:接口同时被多个请求调用,可能是由于并发请求过多,导致接口响应慢。

为了找出性能瓶颈,可以使用一些性能分析工具,比如Java Mission Control、.VisualVM等。

2.优化数据库查询

如果接口中存在数据库查询操作,可以通过以下方式优化查询性能:

  • 优化查询语句:确保查询语句使用了正确的索引,避免全表扫描。可以使用数据库的查询优化器来自动生成或者调整查询计划。
  • 缓存查询结果:对于一些数据量较大且不经常变动的查询结果,可以将其缓存在内存中,避免重复查询数据库。可以使用缓存库,比如Redis、Ehcache等。
  • 分批查询:如果查询结果集很大,可以采用分批查询的方式,每次只查询一部分数据,减轻数据库的压力。

3.优化外部依赖调用

如果接口中调用了其他服务或者外部资源,可以通过以下方式优化调用性能:

  • 异步调用:将耗时的调用改为异步方式,可以减少等待时间。可以使用Java的CompletableFuture或者基于消息队列的异步调用方式。
  • 缓存调用结果:对于一些调用结果不经常变动的场景,可以将其缓存起来,避免重复调用。可以使用缓存库,比如Redis、Ehcache等
  • 优化网络通信:如果网络延迟较高,可以考虑使用更快的网络通信协议,比如gRPC、Apache Thrift等。

4.处理高并发压力

如果接口面临高并发压力,可能会导致接口响应慢。可以通过以下方式处理高并发:

  • 负载均衡:使用负载均衡器将请求分发到多个实例上,提高系统容量和吞吐量。可以使用Nginx、HAProxy等。
  • 限流策略:对接口进行限流,控制同时处理的请求数量,避免系统资源被消耗殆尽。可以使用框架自带的限流功能,或者使用第三方库,比如Guava RateLimiter。.
  • 水平扩展:增加系统实例数量,提高系统的处理能力。可以通过添加更多的服务器或者使用容器化技术,比如Docker、Kubernetes等。

11.http的状态码

img

计算机网络常见面试题总结(上) | JavaGuide

  1. Java8的新特性

Java8 新特性实战 | JavaGuide

  1. 重载和重写

Java基础常见面试题总结(上) | JavaGuide

  1. 线程常用的方法

Java线程基本方法详解_java中线程相关的基本方法-CSDN博客

  1. 保证线程串行执行的方法

让线程顺序运行的11种方法_线程池如何保证线程执行顺序-CSDN博客

美团二面

  1. 讲讲java垃圾回收

  2. 垃圾回收算法

1.标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。

  • 该算法分为两个阶段,标记和清除。标记阶段标记所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。该算法最大的问题就是内存碎片严重化,后续可能发生对象不能找到利用空间的问题。

2.复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。

  • 按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。

3.标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

  • 标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。

4.分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

  • 当前商业虚拟机都采用分代收集的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期将内存划分为几块。一般包括年轻代、老年代 和 永久代。

垃圾回收算法详解

  1. 怎么进行分代回收

分代回收详解

  1. 讲讲JVM内存结构

JVM内存结构详解

  1. 堆内部的结构是怎样的

Java 世界中“几乎”所有的对象都在堆中分配,但是,随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。

在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:

  1. 新生代内存(Young Generation)
  2. 老生代(Old Generation)
  3. 永久代(Permanent Generation)

JDK 8 版本之后 PermGen(永久代) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存。

大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。不过,设置的值应该在 0-15,否则会爆出以下错误:

MaxTenuringThreshold of 20 is invalid; must be between 0 and 15

堆详解

  1. 讲讲常用的设计模式

img

7.写一段策略模式的代码

六种常见的设计模式详解

8.优先队列是基于什么数据结构实现的

优先队列 (Priority Queue) 从底层结构上来讲并非线性的数据结构,它一般是由堆来实现的。

  1. 在每个元素入队时,优先队列会将新元素其插入堆中并调整堆。
  2. 在队头出队时,优先队列会返回堆顶元素并调整堆。

总而言之,不论我们进行什么操作,优先队列都能按照某种排序方式进行一系列堆的相关操作,从而保证整个集合的有序性

虽然优先队列的底层并非严格的线性结构,但是在我们使用的过程中,我们是感知不到的,从使用者的眼中优先队列可以被认为是一种线性的数据结构:一种会自动排序的线性队列。

9.堆是怎么实现的,怎么增删改查

堆详解

10.MySQL中主键自增正数和随机整数在性能上有什么区别?如果自增和随机id都是程序给的,MySQL都需要进行查找,那么在写入时会有什么性能区别呢?

  1. 主键自增:
  • 写入性能:自增主键每次插入时都是追加记录,不需要移动其他数据。这意味着在B+Tree索引结

构中,自增主键的插入是高效的,不会触发页分裂(page split)。页分裂是当数据页已满,需要

申请新的数据页并将部分数据移动到新页的过程,这会导致性能下降和空间利用率降低。

  • 存储空间:使用自增主键时,索引的大小相对较小,因为主键是连续的,存储时更紧凑。这也有助

于提高性能,因为连续的主键可以更快地检索。

  • 唯一性:自增主键保证了数据的唯一性,并且是自动生成的,这简化了应用层的逻辑
  1. 随机整数主键:
  • 写入性能:如果使用随机整数作为主键,每次插入数据时都需要在索引中查找该值是否已经存在

这增加了写入的复杂性,并可能导致性能下降。此外,由于随机整数不是有序的,因此更有可能触

发页分裂,从而降低写入性能。

  • 存储空间:使用随机整数作为主键可能会导致索引的大小增加,因为随机值可能不如自增主键那样

紧凑。这可能会占用更多的存储空间,并可能影响查询性能。

​ 在写入时,自增主键和随机整数主键的性能差异主要体现在页分裂和索引查找上。自增主键由于是有序的可以避免页分裂,并且插入时不需要进行额外的索引查找。而随机整数主键可能导致频繁的页分裂和索引查找,从而降低写入性能。

11.MySQL为什么要采用b+树作为底层结构

可以看之前的

12.Java中高并发的情况下,什么情况会出现线程不安全,怎么解决

线程不安全的常见原因:

  1. 多线程调度的随机性:由于多线程是抢占式执行的,这导致多线程调度的随机性和无序性。当多个线程并发执行时,CPU可能会在不同的线程之间切换,导致线程的执行顺序变得不确定。这种不确定性可能导致共享数据的访问和修改出现问题,从而产生线程不安全的情况。
  2. 原子性问题:原子性是指不可分割的最小单位。一行代码可能包含多条CPU指令,如果不满足原子性,当多个线程同时访问和修改共享数据时,可能会出现数据不一致的问题。
  3. 竞态条件:当多个线程并发地访问和操作共享数据时,由于执行顺序的不确定性,可能导致程序出现错误的结果。例如,两个线程同时读取并递增一个变量的值,如果不进行同步控制,可能会导致递增操作被覆盖,最终得到的结果不符合预期。

解决线程不安全的方法:

  1. 使用同步机制:例如synchronized关键字或ReentrantLock等锁机制,可以确保同一时间只有一个线程能韵够访问和修改共享数据,从而避免线程安全问题。
  2. 使用线程安全的集合类:Java提供了一些线程安全的集合类,如ector、Hashtablel以及并发包java.uti1.concurrent下的集合类。这些集合类+内部实现了同步机制,可以在多线程环境下安全使用。
  3. 使用volatile关键字:volatile关键字可以确保变量的可见性,即当一个线程修改了变量的值,其他线程能够立即看到这个修改。这对于避免线程安全问题很有帮助。
  4. 使用原子类:Java提供了jawa.uti1.concurrent.atomic包下的原子类,如Atomi cInteger、AtomicLong等。这些原子类提供了线程安全的原子操作,可以简化线程安全的实现。
  5. 避免死锁:在设计多线程程序时,应注意避免死锁的发生。死锁是指两个或更多线程无限期地等待一个永远不会发生的条件。可以使用避免死锁的算法,例如按序申请锁,或设置超时时间等方法来避免死锁。

13.ReenTrantLock是怎么实现的

ReenTrantLock详解

某厂Java实习面经

  1. @Autowired和@resourse的区别

@Resource 和@Autowired 这两个注解的作用都是在 Spring 生态里面去实现 Bean

的依赖注入。

下面我分别说一下@Autowired 和@Resource 这两个注解。

  • @Autowired 是根据 type 来匹配,@Resource 可以根据 name 和 type 来匹配,

默认是 name 匹配。

  • @Autowired 是 Spring 定义的注解,@Resource 是 JSR 250 规范里面定义的注

解,而 Spring 对 JSR 250 规范提供了支持。

  • @Autowired 如果需要支持 name 匹配,就需要配合@Primary 或者@Qualifier

来实现

@Autowired注意点

  • 注解里面有一个 required 属性默认值是 true,表示强制要求 bean 实例的注入, 在应用启动的时候,如果 IOC 容器里面不存在对应类型的 Bean,就会报错。 当然,如果不希望自动注入,可以把这个属性设置成 false。
  • 如果在 Spring IOC 容器里面存在多个相同类型的 Bean 实例,Spring 启动的时候,会提示一个错误,大概意思原本只能注入一个单实例 Bean,我们可以使用 @Primary 或者@Qualifier 这两个注解来解决。 @Primary 表示主要的 bean,当存在多个相同类型的 Bean 的时候,优先使用声明了 @Primary 的 Bean。@Qualifier 的作用类似于条件筛选,它可以根据 Bean 的名字找到需要装配的目标 Bean。

@Resource 的注意点

  • @Resource 是 JDK 提供的注解,只是 Spring 在实现上提供了这个注解的功能支持。 它的使用方式和@Autowired 完全相同,(如图)最大的差异于@Resource 可以支持 ByName 和 ByType 两种注入方式。 如果使用 name,Spring 就根据 bean 的名字进行依赖注入,如果使用 type,Spring 就根据类型实现依赖注入。
  • 如果两个属性都没配置,就先根据定义的属性名字去匹配,如果没匹配成功,再根据类型匹配。两个都没匹配到,就报错
  1. Redis的持久化策略,AOF数据量大,怎么解决?

AOF重写

Redis持久化详解

3. 缓存穿透,缓存雪崩,缓存击穿,解决办法

4. ArrayList默认大小

从无参构造来看我认为应该是0,但是一般都认为是10

ArrayList详解

5. HashMap线程安全吗,如何保证线程安全?

线程不安全,用concurrentHashMap

详解看之前

  1. JVM内存结构

  2. 虚拟机栈由什么组成

  • 局部变量表 主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
  • 操作数栈 主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。
  • 动态链接 主要服务一个方法需要调用其他方法的场景。Class 文件的常量池里保存有大量的符号引用比如方法引用的符号引用。当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用,这个过程也被称为 动态连接
  • 方法返回地址

详解

  1. 堆的内存结构,对象什么时候进入幸存者区

  2. GC算法,分别在什么时候使用

垃圾回收算法详解

  1. 对象什么情况下不会一直进入老年代

  2. 双亲委派机制

查找上方

  1. 类加载的阶段有哪些

类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段::加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中,验证、准备和解析这三个阶段可以统称为连接(Linking)。

类加载详解

  1. 事务的四大特性

ACID,详解看上方

  1. 事务隔离级别,innodb默认哪一种

读未提交,读已提交,可重复读,序列化。默认是可重复读,详解看上方

  1. MVCC底层原理

innodb对MVCC的实现依赖于

  1. 隐藏字段

在内部,InnoDB 存储引擎为每行数据添加了三个隐藏字段:

  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除
  • DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
  • DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引
  1. ReadView

主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”

主要有以下字段:

  • m_low_limit_id:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见
  • m_up_limit_id:活跃事务列表 m_ids 中最小的事务 ID,如果 m_ids 为空,则 m_up_limit_idm_low_limit_id。小于这个 ID 的数据版本均可见
  • m_idsRead View 创建时其他未提交的活跃事务 ID 列表。创建 Read View时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。m_ids 不包括当前事务自己和已提交的事务(正在内存中)
  • m_creator_trx_id:创建该 Read View 的事务 ID

img

  1. undolog

undo log 主要有两个作用:

  • 当事务回滚时用于将数据恢复到修改前的样子
  • 另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现非锁定读

详解

  1. MySQL默认存储引擎?什么情况下用innodb?

详解

  1. TCP和UDP的区别

详解

  1. HTTP和TCP的区别

  2. 三次握手和四次挥手

详解

  1. IOC,AOP

详解

  1. Bean的生命周期,Before和After有什么作用

详解

  1. Springboot使用过哪些注解?

在使用SpringBoot的过程中,几乎没有了XML配置,采用注解+JavaConfig方式开发,这样做的好处是:

  • 采用纯java代码,不在需要配置繁杂的xml文件
  • 在配置中也可享受面向对象带来的好处
  • 类型安全对重构可以提供良好的支持
  • 减少复杂配置文件的同时亦能享受到springIoC容器提供的功能

常见注解

  • @SpringBootApplication

这个注解很常见了,每次在启动SpringBoot项目的时候,都会见到它,它作用在类上,标识该类为SpringBoot项目启动类。并且让SpringBoot自动给程序进行必要的配置,等同于@Configuration、@EnableAutoConfiguration、@ComponentScan这三个注解。

  • @Configuration

相当于传统Spring的xml配置文件。

如果第三方库需要用到xml文件,建议通过@Configuration类作为项目的配置主类,可以使用@ImportResource 注解加载xml配置文件。

  • @EnableAutoConfiguration 自动配置。

SpringBoot自动配置(auto-configuration),尝试根据你添加的启动器(starter)自动配置你的SpringBoot应用。

你可以将@EnableAutoConfiguration注解或者@SpringBootApplication注解添加到一个@Configuration类上来选择自动配置。

如果发现应用了你不想要的特定自动配置类,你可以使用@EnableAutoConfiguration注解的排除属性来禁用它们。

  • @ComponentScan

如果某个类加上@ComponentScan注解,则该类会自动发现扫描组件。

也就是说,如果扫描到有@Component、@Controller、@Service等这些注解的类,并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。

我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。如果没有配置的话,SpringBoot会扫描启动类所在包下以及子包下的使用了@Component、@Controller、@Service、@Repository等注解的类。

  • @Controller

@Controller注解用于定义控制器类,在SpringBoot中由控制器负责将用户发来的URL请求转发到对应的服务接口(Controller层)。

一般这个注解用在类上,控制器方法需要加上@RequestMapping注解。

  • @ResponseBody

如果控制器方法加上@ResponseBody注解,该方法的返回结果将直接写入HTTP response body中,一般在异步获取数据时使用,用于构建RESTful的api。

在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。

比如异步获取json数据,加上@Responsebody后,会直接返回json数据。该注解一般会配合@RequestMapping一起使用。

  • @RestController

@RestController注解是@Controller和@ResponseBody的合集。

使用在类上,表示该类是控制器,并且类中所有控制器方法的返回值直接填入HTTP响应体中,是RESTful风格的控制器,控制器方法返回JSON数据。

  • @RequestMapping

提供路由信息,负责URL到Controller中的具体方法的映射。

  • @Import

用来导入其他配置类(加了@Configuration注解的类)。

  • @ImportResource

用来加载xml配置文件。

  • @PathVariable

获取URL上的参数(Restful风格接口)。

  • @Service

一般用于修饰service层的组件

  • @Repository

使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。

  • @Bean

用@Bean标注方法等价于XML中配置的bean,意思是产生一个bean,并交给SpringBoot管理。

  • @Value

注入SpringBoot中的配置文件——application.properties配置的属性的值。

  • @Inject

等价于默认的@Autowired,只是没有required属性。

  • @Component

泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

  • @AutoWired

自动导入依赖的bean。byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。当加上(required=false)时,就算找不到bean也不报错。

  • @Qualifier

当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者。

  • @Resource(name=”name”,type=”type”)

没有括号内内容的话,默认byName。与@Autowired干类似的事。

  • @JsonBackReference

解决嵌套外链问题。

  • @RepositoryRestResourcepublic

配合spring-boot-starter-data-rest使用。

全局异常注解

  • @ControllerAdvice

包含@Component,可以被扫描到。统一处理异常。

  • @ExceptionHandler(Exception.class)

用在方法上面表示遇到这个异常就执行以下方法。

JPA注解

JPA是ORM思想的规范接口,存在两个核心:

建立表与实体类的映射。

建立表字段与实体类属性映射,操作实体类就是操作表。

Hibernate框架作为JPA的实现者之一。

@Entity&@Table(name=“表名”)

@Entity注解表明这是一个实体类。

@Table(name=“表名”)映射一张表,但是如果表名和实体类名相同的话,@Table可以省略。

@MappedSuperClass

用在确定是父类的entity上。父类的属性子类可以继承。

@NoRepositoryBean

一般用作父类的repository,有这个注解,SpringBoot不会去实例化该repository。

@Column

建立表字段与实体类属性映射,如果字段名与列名相同,则可以省略。

@Id

表示该属性为主键。

@Transient

表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。

如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic。

@Basic

@Basic(fetch=FetchType.LAZY):标记可以指定实体属性的加载方式

@GeneratedValue

@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = “repair_seq”)表示主键生成策略是sequence(可以为Auto、IDENTITY、native等,Auto表示可在多个数据库间切换),指定sequence的名字是repair_seq。

@SequenceGeneretor

@SequenceGeneretor(name = “repair_seq”, sequenceName = “seq_repair”, allocationSize = 1):name为sequence的名称,以便使用,sequenceName为数据库的sequence名称,两个名称可以一致。

@JsonIgnore

作用是json序列化时将Java bean中的一些属性忽略掉,序列化和反序列化都受影响。

@OneToOne、@OneToMany、@ManyToOne

对应多表之间的一对一,一对多,多对一关系。

  1. MyBatis怎么使用的

详解

  1. 主从redis集群了解吗

详解

  1. git怎么用的

详解

  1. Linux怎么抓一个日志

详解

一:查看日志

1.使用cat查看日志

1)cat test.log 查看test.log日志文件的所有信息

2)cat -n test.log | grep “#” 查看test.log日志文件的中包含“#” 的行

3)分页 cat -n test.log | grep “get” | more 查看日志文件test.log 中包含“get”的字符,然后分页

4)重定向 cat -n test.log | grep “get” >test1.log 查看日志文件test.log 中包含“get”的字符,然后写入到文件test1.log 中,(>写入覆盖文件test1.log, >>追加到test1.log 文件后)

2.使用tail查看日志

1)tail -n 10 test.log 查看日志文件最后10行数据(和tail -n -10 test.log 意义一样)

2)tail -n +10 test.log 查看日志文件从10行开始,到末尾结束

  1. tail -f test.log -n 1000 动态获取查看最后1000条数据

3.使用head查看日志

1)head -n 10 test.log 查看日志文件前10行数据(和head -n +10 test.log 意义一样)

2)head -n -10 test.log 查看日志文件从头部开始,到倒数第十行结束

二:拉取日志

1)如果使用securecrt(secureCRTPortable)软件,可以执行 “ sz test.log” 命令,将test.log 日志文件下载到本地,

如果sz命令不存在的话,执行“yum install lrzsz” 命令安装,安装好后使用sz命令下载;

小米Java实习一面

  1. 网络五层介绍

  2. http和https的区别

详解

  1. https安全认证原理

详解

  1. 平时接触过多线程没,说说用过的锁

lock,sy,分布式锁

具体看一下之前的详解

  1. sy关键字的实现原理

详解

  1. sql优化手段

  2. 索引使用原则

  3. 大量的数据查询慢怎么取考虑,入手点在哪?

看慢查询日志,explain查看,具体详解看前面的分析

  1. 有一些加索引的数据经常要增删改,又要经常查,怎么平衡这件事?

这个的话,我问了一下AI

在使用MySQL数据库时,索引对于提高查询效率至关重要,但同时也会对增删改操作产生影响,因为每次修改数据时,MySQL都需要更新索引。为了平衡查询效率和写操作的性能,可以采取以下几种策略:

  1. 选择合适的索引类型:根据数据的特点选择合适的索引类型,比如对于经常进行范围查询的列使用B-Tree索引,对于经常进行等值查询的列可以使用哈希索引。
  2. 索引列的选择:只对经常参与查询条件的列创建索引,避免对所有列都创建索引,这样可以减少索引的维护开销。
  3. 使用复合索引:如果查询经常涉及多个列,可以考虑使用复合索引,这样可以减少索引的数量,同时提高查询效率。
  4. 定期优化索引:随着数据量的增加,索引可能会变得碎片化,定期使用OPTIMIZE TABLE命令来优化索引可以提高性能。
  5. 写操作批量处理:尽量将写操作批量处理,减少单次写操作的频率,这样可以减少索引更新的次数。
  6. 使用事务:合理使用事务可以减少索引的更新次数,因为事务中的写操作可以一次性提交,减少了索引的单独更新。
  7. 分区表:对于非常大的表,可以考虑使用分区表来提高查询和写操作的性能。
  8. 延迟索引维护:对于写入密集型的操作,可以考虑在写入操作完成后再更新索引。
  9. 监控和分析:使用MySQL的慢查询日志和性能监控工具来分析查询性能,根据实际情况调整索引策略。
  10. 读写分离:如果读操作远多于写操作,可以考虑使用读写分离的架构,将读操作和写操作分散到不同的服务器上。
  11. 使用缓存:对于经常被查询但很少变更的数据,可以使用缓存来减少数据库的查询压力。
  12. 考虑NoSQL解决方案:如果数据的写入非常频繁,并且对查询的实时性要求不高,可以考虑使用NoSQL数据库,如MongoDB或Cassandra,它们通常对写入操作有更好的支持。

每种策略都有其适用场景,需要根据具体的业务需求和数据特点来选择最合适的方案。

  1. 可达性分析回收入手点

  2. 可达性分析哪些类型可以做root

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象
  • JNI(Java Native Interface)引用的对象

可达性分析法详解

  1. 年轻代,老年代的设计理由,有什么好处

在Java虚拟机(JVM)中,堆内存被分为不同的区域,其中“年轻代”和“老年代”是堆内存管理中两个重要的概念。它们各自有不同的设计理由和好处:

  • 年轻代(Young Generation)

设计理由:

  1. 对象生命周期短:大多数对象都是短暂存在的,年轻代用于存放新创建的对象。
  2. 频繁垃圾回收:年轻代的对象频繁创建和销毁,因此需要频繁地进行垃圾回收。
  3. 空间小:年轻代的空间相对较小,便于管理。

好处:

  1. 快速回收:由于对象生命周期短,年轻代可以快速回收不再使用的对象。
  2. 减少停顿时间:年轻代的垃圾回收(Minor GC)通常比老年代的垃圾回收(Major GC)要快,减少了程序的停顿时间。
  3. 内存分配效率高:年轻代的内存分配可以更加高效,因为可以快速地分配和回收内存。
  • 老年代(Old Generation)

设计理由:

  1. 对象生命周期长:一些对象存活时间较长,会被提升到老年代。
  2. 垃圾回收频率低:老年代的对象不需要频繁进行垃圾回收。
  3. 空间大:老年代的空间相对较大,用于存放长期存活的对象。

好处:

  1. 减少频繁垃圾回收:老年代的对象由于生命周期较长,不需要频繁进行垃圾回收,减少了垃圾回收的开销。
  2. 优化垃圾回收算法:老年代可以使用不同的垃圾回收算法,如标记-清除、标记-整理等,这些算法更适合处理长期存活的对象。
  3. 稳定内存使用:老年代的内存使用相对稳定,有助于避免频繁的垃圾回收导致的性能波动。

​ 年轻代和老年代的设计使得JVM可以更有效地管理内存,通过区分对象的生命周期来优化垃圾回收的性能。年轻代的快速回收机制适合处理大量短命对象,而老年代则适合长期存活的对象,两者的结合使用可以提高内存管理的效率和程序的运行性能。

  1. 引擎InnoDB的ACID的实现原理

  • A 表示 Atomic 原子性,也就是需要保证多个 DML 操作是原子的,要么都成功, 要么都失败。 那么,失败就意味着要对原本执行成功的数据进行回滚,所以 InnoDB 设计了一个 UNDO_LOG 表,在事务执行的过程中, 把修改之前的数据快照保存到UNDO_LOG里面,一旦出现错误,就直接从UNDO_LOG 里面读取数据执行反向操作就行了。
  • C 表示一致性,表示数据的完整性约束没有被破坏,这个更多是依赖于业务层面的保证,数据库本身也提供了一些,比如主键的唯一余数,字段长度和类型的保证等等
  • I 表示事物的隔离性,也就是多个并行事务对同一个数据进行操作的时候,如何避免多个事务的干扰导致数据混乱的问题。 而 InnoDB 实现了 SQL92 的标准,提供了四种隔离级别的实现。分别是: RU(未提交读) RC(已提交读) RR(可重复读) Serializable(串行化) InnoDB 默认的隔离级别是 RR(可重复读),然后使用了 MVCC 机制解决了脏读和不可重复读的问题,然后使用了行锁/表锁的方式解决了幻读的问题。
  • D表示持久性,也就是只要事务提交成功,那对于这个数据的结果的影响一定是永久性的。不能因为宕机或者其他原因导致数据变更失效。 理论上来说,事务提交之后直接把数据持久化到磁盘就行了,但是因为随机磁盘 IO 的效率确实很低,所以 InnoDB 设计了 Buffer Pool 缓冲区来优化,也就是数据发生变更的时候先更新内存缓冲区,然后在合适的时机再持久化到磁盘。 那在持久化这个过程中,如果数据库宕机,就会导致数据丢失,也就无法满足持久性了。所以 InnoDB 引入了 Redo_LOG 文件,这个文件存储了数据被修改之后的值,当我们通过事务对数据进行变更操作的时候,除了修改内存缓冲区里面的数据以外,还会把本次修改的值追加到 REDO_LOG 里面。 当提交事务的时候,直接把 REDO_LOG 日志刷到磁盘上持久化,一旦数据库出现宕机,在 Mysql 重启在以后可以直接用 REDO_LOG 里面保存的重写日志读取出来,再执行一 遍从而保证持久性。
  • 因此,在我看来,事务的实现原理的核心本质就是如何满足 ACID 的,在 InnDB 里面用到了 MVCC、行锁表锁、UNDO_LOG、REDO_LOG 等机制来保证。
  1. IOC源码讲讲印象最深的地方

详解

  1. 工厂模式设计

查看之前的六中常见设计模式

  1. #{}和${}的区别

首先,Mybatis 提供到的#号占位符和$号占位符,都是实现动态 SQL 的一种方式,通过这两种方式把参数传递到 XML 之后, 在执行操作之前,Mybatis 会对这两种占位符进行动态解析。(如图)#号占位符,等同于 jdbc 里面的?号占位符。 它相当于向 PreparedStatement 中的预处理语句中设置参数, 而 PreparedStatement 中的 sql 语句是预编译的,SQL 语句中使用了占位符,规定了sql 语句的结构。 并且在设置参数的时候,如果有特殊字符,会自动进行转义。 所以#号占位符可以防止 SQL 注入。而使用$的方式传参,相当于直接把参数拼接到了原始的 SQL 里面,Mybatis 不会对它进行特殊处理。所以$和#最大的区别在于,前者是动态参数,后者是占位符, 动态参数无法防止 SQL 注入的问题,所以在实际应用中,应该尽可能的使用#号占位符。 另外,$符号的动态传参,可以适合应用在一些动态 SQL 场景中,比如动态传递表名、动态设置排序字段等

  1. mybatis中xml配置解析原理

  2. mybatis从解析到执行sql语句的原理

  3. 一级缓存、二级缓存的原理

mybatis详解

Mybatis 里面设计了两级缓存来提升数据的检索效率,避免每次数据的访问都需要去查询数据库。先来看一级缓存,它是 SqlSession 级别的缓存,也叫本地缓存,因为每个用户在执行查询的时候都需要使用 SqlSession 来执行,为了避免每次都去查数据库,MyBatis 把查询出来的数据保存到 SqlSession 的本地缓存中,后续的 SQL 如果命中缓存,就可以直接从本地缓存读取。

那如果想要实现跨 SqlSession 级别的缓存?一级缓存就无法实现了,因此,MyBatis 引入了二级缓存。当多个用户在查询数据的时候,只要有任何一个 SqlSession 拿到了数据就会放入到二级缓存里面,其他的 SqlSession 就可以从二级缓存加载数据。

接下来,我给大家详细分析一下 MyBatis 缓存机制的实现原理。先来看一级缓存的实现原理: 在 SqlSession 里面持有一个 Executor 的对象,每个 Executor 中有一个 LocalCache 对象。 当用户发起查询的时候,MyBatis 会根据执行语句在 Local Cache 里面查询,如果没命中,再去查询数据库并写入到 LocalCache,否则直接返回。

所以,一级缓存的生命周期只在 SqlSession 级别,而且在多个 SqlSession 或者分布式环境下,可能会导致数据库写操作出现脏数据。那这个时候,就要使用二级缓存。下面来看二级缓存的具体实现原理:

二级缓存使用了一个叫做 CachingExecutor 的对象,对 Executor 进行了装饰,在进入一级缓存的查询流程之前,会先通过 CachingExecutor 进行二级缓存的查询。

开启二级缓存以后,会被多个 SqlSession 共享,所以它是一个全局缓存。因此它的查询流程是先查二级缓存,再查一级缓存,最后再查数据库。

另外,MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时缓存粒度也能够控制到 Name Space 级别,并且还可以通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强

  1. springboot自动装配原理

详解2

自动装配,简单来说就是自动把第三方组件的 Bean 装载到 Spring IOC 器里面,不需

要开发人员再去写 Bean 的装配配置。

在 Spring Boot 应用里面,只需要在启动类加上@SpringBootApplication 注解就可

以实现自动装配。

@SpringBootApplication 是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。

自动装配的实现主要依靠三个核心关键技术。

  1. 引入 Starter 启动依赖组件的时候,这个组件里面必须要包含@Configuration 配置类,在这个配置类里面通过@Bean 注解声明需要装配到 IOC 容器的 Bean 对象。
  2. 这个配置类是放在第三方的 jar 包里面,然后通过 SpringBoot 中的约定优于配置思想,把这个配置类的全路径放在 classpath:/META-INF/spring.factories 文件中。这样 SpringBoot 就可以知道第三方 jar 包里面的配置类的位置,这个步骤主要是用到了 Spring 里面的 SpringFactoriesLoader 来完成的。
  3. SpringBoot 拿到第三方 jar 包里面声明的配置类以后,再通过 Spring 提供的ImportSelector 接口,实现对这些配置类的动态加载。在我看来,SpringBoot 是约定优于配置这一理念下的产物,所以在很多的地方,都会看到这类的思想。它的出现,让开发人员更加聚焦在了业务代码的编写上,而不需要去关心和业务无关的配置。

其实,自动装配的思想,在 SpringFramework3.x 版本里面的@Enable 注解,就有了实现的雏形。@Enable 注解是模块驱动的意思,我们只需要增加某个@Enable 注解, 就自动打开某个功能,而不需要针对这个功能去做 Bean 的配置,@Enable 底层也是帮我们去自动完成这个模块相关 Bean 的注入。

  1. Redis为什么比mysql快

Redis 内部做了非常多的性能优化,比较重要的有下面几点:

  1. Redis 基于内存,内存的访问速度比磁盘快很多;

  2. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用;

  3. Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。

  4. Redis 通信协议实现简单且解析高效。

  5. DDD架构的理解

抽空看一下

详解

美团到家一面

  1. jvm讲一下

  2. ArrayList扩容讲一下

  3. reids为什么快

  4. 索引为什么用b+树,为什么不用别的

  5. 事务ACID是什么

  6. 原子性是怎么保证的

  7. Spring的注解讲一下

腾讯PCG一面

  1. zset底层数据结构

详解

  1. hash在redis中的底层实现

详解

  1. mysql的ACID,隔离级别,默认隔离级别,Redis事务

看前面

  1. redis做缓存 缓存一致性问题,延迟双删

详解

  1. 缓存穿透,布隆过滤器原理

布隆过滤器详解

  1. 计算机网络TCP,UDP,UDP出错了如何处理,TCP的可靠性如何保障,time_wait

看TCP和UDP详解

滴滴后端一面

  1. 进程,线程,协程的区别

  2. 进程的五种状态,如何切换

详解

  1. http请求头有哪些

字段名说明示例
Accept能够接受的回应内容类型(Content-Types)Accept: text/plain
Accept-Charset能够接受的字符集Accept-Charset: utf-8
Accept-Encoding能够接受的编码方式列表Accept-Encoding: gzip, deflate
Accept-Language能够接受的回应内容的自然语言列表Accept-Language: en-US
Authorization用于超文本传输协议的认证的认证信息Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control用来指定在这次的请求/响应链中的所有缓存机制 都必须 遵守的指令Cache-Control: no-cache
Connection该浏览器想要优先使用的连接类型Connection: keep-alive Connection: Upgrade
Cookie服务器通过 Set- Cookie (下文详述)发送的一个 超文本传输协议CookieCookie: $Version=1; Skin=new;
Content-Length以 八位字节数组 (8位的字节)表示的请求体的长度Content-Length: 348
Content-Type请求体的 多媒体类型Content-Type: application/x-www-form-urlencoded
Date发送该消息的日期和时间Date: Tue, 15 Nov 1994 08:12:31 GMT
Expect表明客户端要求服务器做出特定的行为Expect: 100-continue
Host服务器的域名(用于虚拟主机 ),以及服务器所监听的传输控制协议端口号Host: en.wikipedia.org:80 Host: en.wikipedia.org
If-Match仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要作用时,用作像 PUT 这样的方法中,仅当从用户上次更新某个资源以来,该资源未被修改的情况下,才更新该资源If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since允许在对应的内容未被修改的情况下返回304未修改If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
If-None-Match允许在对应的内容未被修改的情况下返回304未修改If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range如果该实体未被修改过,则向我发送我所缺少的那一个或多个部分;否则,发送整个新的实体If-Range: “737060cd8c284d8af7ad3082f209582d”
Range仅请求某个实体的一部分Range: bytes=500-999
User-Agent浏览器的浏览器身份标识字符串User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0
Origin发起一个针对 跨来源资源共享 的请求Origin: http://www.example-social-network.com
  1. 常见的状态码

1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。

2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。

  • 「200 OK」是最常见的成功状态码,表示一切正常。如果是非 HEAD 请求,服务器返回的响应头都会有 body 数据。
  • 「204 No Content」也是常见的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据。
  • 「206 Partial Content」是应用于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。

3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。

  • 「301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
  • 「302 Found」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
  • 「304 Not Modified」不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,也就是告诉客户端可以继续使用缓存资源,用于缓存控制。

4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。

  • 「400 Bad Request」表示客户端请求的报文有错误,但只是个笼统的错误。
  • 「403 Forbidden」表示服务器禁止访问资源,并不是客户端的请求出错。
  • 「404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。

5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。

  • 「500 Internal Server Error」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
  • 「501 Not Implemented」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
  • 「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
  • 「503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。

详解

  1. GET与POST

详解

  1. 常用的集合,hashtable和concurrentHashMap有什么区别

区别

  1. java的垃圾回收器有哪些

img

详解

  1. mysql的几种事务隔离级别

读未提交,读已提交,可重复读,可串行化

  1. 脏读,幻读,不可重复读

脏读(Dirty read)

一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据,这也就是脏读的由来。

例如:事务 1 读取某表中的数据 A=20,事务 1 修改 A=A-1,事务 2 读取到 A = 19,事务 1 回滚导致对 A 的修改并未提交到数据库, A 的值还是 20。

不可重复读(Unrepeatable read)

指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 再次读取 A =19,此时读取的结果和第一次读取的结果不同。

幻读(Phantom read)

幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

例如:事务 2 读取某个范围的数据,事务 1 在这个范围插入了新的数据,事务 2 再次读取这个范围的数据发现相比于第一次读取的结果多了新的数据。

不可重复读和幻读有什么区别?

  • 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;
  • 幻读的重点在于记录新增比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。

幻读其实可以看作是不可重复读的一种特殊情况,单独把幻读区分出来的原因主要是解决幻读和不可重复读的方案不一样。

举个例子:执行 deleteupdate 操作的时候,可以直接对记录加锁,保证事务安全。而执行 insert 操作的时候,由于记录锁(Record Lock)只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁(Gap Lock)。也就是说执行 insert 操作的时候需要依赖 Next-Key Lock(Record Lock+Gap Lock) 进行加锁来保证不出现幻读。

  1. Spring:是如何整合MyBatis将Mapper接口注册为Bean的原理?

1.首先MyBatis的Mapper接口核心是JDK动态代理

2.Spring会排除接口,无法注册到IOC容器中

3.MyBatis实现了BeanDefinitionRegistryPostProcessor可以动态注册BeanDefinition

4.需要自定义扫描器(继承Spring内部扫描器ClassPathBeanDefinitionScanner)重写排除接口的方法(isCandidateComponent)

5.但是接口虽然注册成了BeanDefinition但是无法实例化Bean因为接口无法实例化

6.需要将BeanDefinition的BeanClass替换成JDK动态代理的实例(偷天换日)

7.Mybatis通过FactoryBean的工厂方法设计模式可以自由控制Bean的实例化过程,可以在getObject方法中创建JDK动态代理

某厂java后端实习二面

  1. http和https的区别

详解

端口号:HTTP 默认是 80,HTTPS 默认是 443。

URL 前缀:HTTP 的 URL 前缀是 http://,HTTPS 的 URL 前缀是 https://

安全性和资源消耗:HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。

  1. tcp和udp的区别

是否面向连接:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。

是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。

是否有状态:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(这很渣男!)。

传输效率:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。

传输形式:TCP 是面向字节流的,UDP 是面向报文的。

首部开销:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。

是否提供广播或多播服务:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;

TCPUDP
是否面向连接
是否可靠
是否有状态
传输效率较慢较快
传输形式字节流数据报文段
首部开销20 ~ 60 bytes8 bytes
是否提供广播或多播服务

详解

  1. 三次握手和四次挥手

详解

  1. 说说事物的四大特性 各个实现

  • 持久性是通过 redo log (重做日志)来保证的;
  • 原子性是通过 undo log(回滚日志) 来保证的;
  • 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
  • 一致性则是通过持久性+原子性+隔离性来保证;
  1. 脏读和幻读的区别

  • 脏读:读到其他事务未提交的数据;
  • 不可重复读:前后读取的数据不一致;
  • 幻读:前后读取的记录数量不一致。
  1. 对于springboot的理解,常用注解

首先,说Springboot必须先说Spring,Spring提供了一种简单的方法通过依赖注入和面向切面编程,使用简单的Java对象实现了EJB的功能

但是虽然组件代码是轻量级的,但是配置确是重量级的,需要大量的xml,Spring2.5引入了基于注解的组件扫描,消除了大量针对程序自身组件的显式xml配置,Spring3.0引入基于Java的配置,这是一种类型安全的可重构配置方式,可以代替xml

尽管如此,我们依然没能逃脱配置的魔爪,开启某些Spring的特性的时候,依然需要显式的配置,比如事务管理,SpringMVC,还有一些第三方的配置,也需要显式的xml配置,比如thymeleaf的web视图,配置servlet和过滤器等,组件扫描减少了配置量,但是依然需要大量的配置

光是xml文件的就可能需要占用我们大量的时间,除此之外,相关库的依赖也非常让人头疼,不同库之间的版本冲突非常常见

所以就有了Springboot

Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可

  • 开发基于Spring的应用程序很容易
  • SpringBoot项目所需的开发或工程时间明显减少,通常会提高整体的生厂力
  • SpringBoot不需要编写大量的样板代码,xml配置和注释
  • Spring引导应用程序可以容易地与Spring生态系统集成,SpringJDBC、SpringORM,SpringData,SpringSecurity等
  • 遵循默认配置,以减少开发工作,默认配置可以修改
  • 提供嵌入式的HTTP服务器,Tomcat和Jetty,可以轻松的开发和测试web应用程序
  • 提供了命令行接口工具(CLI),用于开发和测试springBoot应用程序,如Java或者Groovy
  • 提供和很多的插件,可以使用内置的工具(Maven或者Gradle)开发和测试SpringBoot应用程序
  1. AOP的使用,对底层的了解

详解

  1. 创建索引需要注意什么

看前面的

  1. 索引失效场景

  2. 聚簇索引和非聚簇索引的区别

  3. 各类垃圾回收算法以及优缺点

详解

京东Java实习业务面试

  1. 说说类的加载机制

详解

  1. 说说JVM

详解

  1. MongoDB和MySQL的区别以及各自的优势

MongoDB和MySQL的区别:

  1. 数据库类型:

MongoDB是一个文档型数据库,它存储的是文档型数据(如JSON格式)。

MySQL是一个关系型数据库,它存储的是结构化的表格数据。

  1. 数据模式:

MongoDB采用动态模式,这意味着同一个集合中的文档不需要有相同的字段和结构。

MySQL则需要预先定义字段。

  1. 查询语言:

MongoDB使用面向文档的查询语言,可以方便地对JSON格式的数据进行查询。

MySQL则使用标准的SQL语言进行数据库查询。

  1. 索引:

MongoDB可以对任何属性建立索引,这使得数据检索更加灵活。

MySQL则主要对表中的列建立索引。

  1. 扩展性:

MongoDB是一个基于分布式文件存储的数据库,可以方便地扩展到大量的数据和高并发场景。

MySQL的扩展性相对有限,通常通过垂直扩展(如升级硬件)来提高性能。

  1. 事务处理:

MongoDB:不支持传统的事务处理,这可能在某些需要高度数据一致性的场景中成为限制。

MySQL在复杂事务处理和数据完整性方面更可靠,提供了完善的事务管理机制。

MongoDB的优势:

  1. 灵活性:由于MongoDB采用文档型存储和动态模式,它非常适合处理非结构化或半结构化数据,无需事先定义严格的数据模式。
  2. 易于扩展:MongoDB的分布式存储架构使得它能够轻松应对大量数据和高并发场景,非常适合构建大型、复杂的应用系统。
  3. 实时性:MongoDB对写入操作有较低的延迟,这使其非常适合实时应用,如物联网、在线游戏等。

MySQL的优势:

  1. 性能优越:MySQL支持大量的并发连接,同时拥有很高的查询速度,适用于各种规模的应用场景。

  2. 易用性:MySQL使用标准的SQL语言,对于熟悉SQL的开发人员来说非常友好,上手成本低。

  3. 灵活性:虽然MySQL是关系型数据库,但它支持多种存储引擎,用户可以根据不同需求选择不同的存储擎。

  4. 高可靠性:MySQL在稳定性方面表现出色,能的够在处理高负载的情况下保持高可靠性,适合构建关键业务应用。

  5. MySQL的事务

详解

  1. 说说Redis的淘汰机制

详解

  1. Redis常用数据类型

详解

用友Java开发面经

  1. java的基本数据类型

img

  1. int和Integer的区别

Integer是int的包装类

  1. ArrayList和HashMap的底层实现

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于等于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap 总是使用 2 的幂作为哈希表的大小。

  1. 数据库的ACID

  2. Mysql的原子性是用什么去实现的

undolog

  1. 事务的隔离级别

读未提交,读已提交,可重复读,可串行化

  1. 什么是索引

索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。

详解

  1. innoDB默认使用什么类型的索引

b+树

  1. b+树和b树的区别

看上

  1. 什么是索引下推

详解

  1. TCP协议是属于那一层的

传输层

  1. 浏览器地址栏输入url会发生什么

  2. Cookie和Session的区别

Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。

Cookie 一般用来保存用户信息 比如 ① 我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;② 一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③ 登录一次网站后访问网站其他页面不需要重新登录。Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。

Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。

Cookie 存储在客户端中,而 Session 存储在服务器上,相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。

  1. Session Storage和Local Storage的区别

详解

  1. IOC和AOP是什么

详解

招银后端一面

  1. MySql优化

四个层面:

硬件和操作系统层面的优化

架构设计层面的优化

MySQL 程序配置优化

SQL 优化

  1. 创建索引的要求,方法,规范

索引的创建

定义有主键和外键的字段列一定要建立索引(数据库默认建立主键索引);

对于查询频率高,频繁作为 WHERE 条件且不同值较多的列,以及频繁出现在关键字ORDER BY、GROUP BY、DISTINCT后面的列,需要建立索引;

经常与其他表进行JOIN连接的表,在连接字段上应该建立索引;

对于那些查询中很少涉及的列,重复值比较多的列(一般为逻辑型字段,比如性别,只有男、女、其他三种值,这种字段区分度不高)不要建立索引;

频繁更新的字段不适合建立索引;

索引应该建在小字段上,对于定义为text等文本数据类型的列不要建立索引;

表的数据量很少的情况下不适合建立索引;

限制每张表上的索引数量,不超过5个;

  1. 索引失效的场景

  2. 联合索引不满足最左匹配原则

  3. 使用了select *

  4. 索引列参与运算

  5. 索引列参使用了函数

  6. 错误的Like使用

  7. 类型隐式转换

  8. 使用OR操作

  9. 两列做比较

  10. 不等于比较

  11. is not null

  12. not in和not exists

  13. order by导致索引失效(主键使用不会失效)

  14. 参数不同导致索引失效

详解

  1. 写一个sql语句,找出表A中重复的业务字段数据b

SELECT b,COUNT (*) as count FROM A GROUP BY b HAVING COUNT (*)>1;
  1. Spring中的事务是用来干什么的

详解

  1. 事务的实现方式

  2. 事务的注解

  3. 如果希望在A方法调用B方法时,即使B方法报错也不回滚A方法的数据,怎么办

img

  1. 设计模式有了解吗

看上面

  1. 单例模式,应用场景,写一下单例模式

双重检查的单例模式

public class Singleton {

private volatile static Singleton instance;

private Singleton(){}

public static Singleton getInstance(){
if (instance==null){
synchronized (Singleton.class){
if (instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}

为什么要double-check?单check不可以吗?

进行了两次if(instance == null)检查,这样就可以保证线程安全了。实例化代码只用执行一次,后面再访问时,直接return实例化的对象。

需要第二重 if(instance ==null)检查的原因

考虑这样一种情况,就是有两个线程同时到达,即同时调用 getInstance() 方法,此时由于 instance == null ,所以很明显,两个线程都可以通过第一重的 singleton== null,进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 singleton == null,而另外的一个线程则会在 lock 语句的外面等待。

当第一个线程执行完 new Singleton()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,此时,如果没有第二重singleton == null 的话,那么第二个线程还是可以调用new Singleton()语句,这样第二个线程也会创建一个 Singleton实例,这样也还是违背了单例模式的初衷的,所以这里必须要使用双重检查锁定。

需要第一重 if(instance ==null)检查的原因

如果去掉第一重 instance == null ,程序还是可以在多线程下安全运行的。

考虑在没有第一重 instance == null 的情况:当有两个线程同时到达,此时,由于lock 机制的存在,假设第一个线程会进入 lock 语句块,并且可以顺利执行 new Singleton(),当第一个线程退出 lock 语句块时, instance这个静态变量已不为null 了,所以当第二个线程进入 lock 时,会被第二重 instance== null 挡在外面,而无法执行 new Singleton(),所以在没有第一重instance== null的情况下,也是可以实现单例模式的。

那么为什么需要第一重 instance== null 呢?

这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,而如果没有第一重instance== null的话,每一次有线程进入getInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重instance== null的话,那么就只有在第一次执行锁定以实现线程同步,而以后的话,便只要直接返回 Singleton 实例就OK了,而根本无需再进入lock语句块了,这样就可以解决由线程同步带来的性能问题了。

第二个问题:为什么要使用volatile

主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话做了下面 3 件事情: 给 instance 分配内存 调用 Singleton 的构造函数来初始化成员变量 将instance对象指向分配的内存空间(执行完这步instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第2步和第3步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,线程一被调度器暂停,此时线程二刚刚进来第一重检查,看到的 instance 已经是非 null 了(没有调用 Singleton 的构造函数来初始化成员变量,里面的值可能是null/false/0,总之不是构造函数中指定的值),然后直接使用线程二返回 instance,程序就会报错或者是看到了非预期的值(因为此时属性的值是默认值而不是所需要的值)。

  1. 为什么MySQL不建议使用NULL作为列默认值

  2. 所有使用NULL值的情况,都可以通过一个有意义的值的表示,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。

  3. NULL值到非NULL的更新无法做到原地更新,更容易发生索引分裂,从而影响性能。

  4. NULL值在timestamp类型下容易出问题,特别是没有启用参数explicit_defaults_for_timestamp

  5. NOT IN、!= 等负向条件查询在有 NULL 值的情况下返回永远为空结果,查询容易出错

  6. Null 列需要更多的存储空间:需要一个额外字节作为判断是否为 NULL 的标志位

  7. NULL通过任一操作符与其它值比较都会得到NULL,除了<=>.

  8. NULL 代表一个不确定的值,就算是两个 NULL,它俩也不一定相等.(像不像 C 中未初始化的局部变量)

  9. 任何有返回值的表达式中有NULL参与时,都会得到另外一个NULL值.

  10. 对含有 NULL 值的列进行统计计算 count(),max(),min(),结果并不符合我们的期望值;使用count() 或者 count(null column)结果不同,count(null column)<=count().

  11. 干扰排序,分组,去重结果,虽然select NULL=NULL的结果为false,但是在我们使用distinct,group by,order by时,NULL又被认为是相同值.

总结

MySQL不建议使用NULL作为列默认值的原因有几个。首先,NULL在SQL中表示“未知”,而不是“空”或“不存在”。当列被设置为NULL默认值时,它可能会导致查询和数据处理时的困惑和错误。

其次,使用NULL默认值可能会导致查询和处理数据时的复杂性增加。在查询中,需要额外处理NULL值的情况,这可能会使查询逻辑变得复杂且容易出错。此外,对NULL值进行数学运算或连接操作时,结果可能不符合预期,需要额外处理或排除NULL值。

另外,使用NULL默认值也会影响索引和性能。在数据库中,索引是提高查询性能的重要手段,但索引通常不会包含NULL值。如果列默认值为NULL,则该列中的大量数据可能为NULL,这会导致索引效率下降,进而影响查询性能。

综上所述,为了避免产生歧义、增加查询复杂性、影响索引和性能,MySQL不建议使用NULL作为列默认值。相反,建议使用适当的默认值或允许空字符串作为默认值,这样可以更直观、简洁地表达数据意图,并提高数据库查询和处理的效率和准确性。

美团门票度假java实习

  1. 介绍一下redission分布式锁

在当今的分布式系统架构中,多个进程或线程可能需要同时访问共享资源,这时候就需要引入分布式锁来保证数据的一致性和系统的稳定性。Redission是一个在Redis基础上实现的Java驻内存数据网格,提供了丰富的分布式对象和服务,其中包括分布式锁。

分布式锁需满足四个条件

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。

  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

  3. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁。

  4. 具有容错性。只要大多数Redis节点正常运行,客户端就能够获取和释放锁。

  5. redission分布式锁底层如何实现

Redisson中的分布式锁基于Redis的Redlock算法实现,通过多个Redis节点来保证锁的分布式特性。Redlock算法要求客户端在获取锁时,需要向多个Redis节点发起请求,只有当大部分节点都成功获取锁时,才认为整个分布式系统成功获取了锁。这样可以有效避免单点故障,提高系统的可用性。

在Redisson中,分布式锁的实现主要包括以下几个步骤:

  1. 客户端向Redis节点发送获取锁的请求,请求中包含锁的唯一标识和锁的过期时间。

  2. Redis节点收到请求后,会检查当前是否有其他客户端持有该锁。如果有,则拒绝请求;如果没有,则将该锁标记为被当前客户端持有,并设置锁的过期时间。

  3. 客户端在持有锁期间,可以对共享资源进行访问。当访问完成后,客户端需要主动释放锁,以便其他客户端可以获取该锁。

  4. 如果客户端在持有锁期间出现宕机或其他异常情况,Redis节点会在锁的过期时间到达后自动释放该锁,避免死锁现象的发生。

  5. 基于setnx实现的分布式锁有哪些缺点

基于setnx的分布式锁存在单点风险,如果存储的分布式锁key挂掉的话,就可能存在丢锁的风险。一旦丢锁,就会造成多个客户端同时握有锁,从而导致分布式锁失败。

  • 客户端A 从master拿到锁lock01
  • master正要把lock01同步(Redis的主从同步通常是异步的)给slave时,突然宕机了,导致lock01没同步给slave
  • 主从切换,slave节点被晋级为master节点
  • 客户端B到master拿lock01照样能拿到。这样必将导致同一把锁被多人使用。
  1. 基于redis如何实现可重入的分布式锁

  • 客户端获取 Redis 连接,通过 setnx 命令设置一个唯一的锁标识符,如果返回值是 1,表示获取到锁,否则锁已经被其他客户端持有,需要等待一段时间后重新获取锁。
  • 在获取到锁之后,客户端需要设置一个过期时间,防止锁无法释放。
  • 客户端释放锁时,需要判断锁是否是自己持有的,如果是,则可以通过 del 命令来释放锁。

重入锁是指同一进程中的线程可以重复获取同一把锁。实现重入锁需要在锁的状态中保存持有者的标识符和持有次数,每次加锁时判断锁的状态中是否已经包含了当前线程的标识符,如果包含则持有次数加一,否则需要等待其他线程释放锁。

释放重入锁时,需要判断当前持有锁的线程是否是当前线程,如果是,则需要将持有次数减一,直到持有次数为 0 时才可以释放锁。

  1. 如何自己实现一个限流算法中的滑动窗口

利用zset实现滑动窗口限流

img

/**
* 滑动窗口限流. 需要注意的是,我们要定期清楚过期的key,否则会导致内存泄漏,可以使用ZREMRANGEBYSCORE方法实现.
* @param key 限流的key
* @param timeWindow 单位时间,秒
* @param limit 窗口大小,单位时间最大容许的令牌数
* @param runnable 成功后的回调方法
*/
public void slidingWindow(String key, int timeWindow, int limit, Runnable runnable) {
Long currentTime = System.currentTimeMillis();
if (redisTemplate.hasKey(key)) {
Long intervalTime = timeWindow * 1000L;
Long from = currentTime - intervalTime;
Integer count = redisTemplate.opsForZSet().rangeByScore(key, from, currentTime).size();
if (count != null && count >= limit) {
throw new RedisLimitException("每" + timeWindow + "秒最多只能访问" + limit + "次.");
}
log.info("from key:{}~{},current count:{}", from, currentTime, count);
}
redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), currentTime);
Optional.ofNullable(runnable).ifPresent(o -> o.run());
}

上面实现了一个基于时间戳为主要窗口依据的滑动窗口限流逻辑,由于zset的数据量会随着时间的流失而变大,所以我们需要定期再根据score来清理它。

/**
* 清期昨天的zset元素,这块应该写个任务调度,每天执行一次,清量需要的zset元素.
* @param key
*/
public void delByYesterday(String key) {
Instant currentInstant = Instant.now();
Instant oneDayAgoInstant = currentInstant.minusSeconds(86400);
long oneDayAgoTimeMillis = oneDayAgoInstant.toEpochMilli();
redisTemplate.opsForZSet().removeRangeByScore(key, 0, oneDayAgoTimeMillis);

}
  1. jvm内存结构

虚拟机栈,本地方法栈,堆,程序计数器,方法区

详解看上面

  1. 本地方法栈和虚拟机栈的区别

本地方法栈(Native Method Stacks)与虚拟机栈(VM Stack)在Java虚拟机中各自扮演着重要的角色,但它们之间存在一些显著的区别。

首先,两者的服务对象不同。虚拟机栈主要为Java方法(也就是字节码)服务,主管Java程序运行,保存方法的局部变量、部分结果,并参与方法的调用和返回。而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。本地方法是用其他语言(如C、C++)编写的方法,通过Java Native Interface(JNI)调用。因此,本地方法栈的存在主要是为了支持本地方法的调用。

其次,两者在结构上也有所不同。虚拟机栈是每个线程创建时都创建的,内部保存的单位是栈帧,对应一次次方法调用,其生命周期与线程一致。而本地方法栈与虚拟机栈一样,也是线程私有的,每个线程都有一个本地方法栈。这意味着每个线程都有自己独立的本地方法栈和虚拟机栈,它们之间进行独立的分配和释放。

最后,两者在异常处理上也有所区别。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时抛出异常。具体来说,当线程请求的栈容量超过固定大小时,会抛出StackOverflowError异常;而当没有足够的内存进行动态拓展时,会抛出OutOfMemoryError错误。

  1. 类加载的过程

加载->连接(验证,准备,解析)->初始化->使用->卸载

  1. redis的常见数据结构

  2. zset的底层数据结构

zset也有两种不同的实现,分别是zipListskipList

img

  1. redis缓存穿透的原因和解决方案

看缓存三兄弟

  1. mysql的索引底层数据结构

b+树

  1. mysql对于两个不同的字段建立索引有几棵b+树

如果是分别建立索引的话会有两棵,这两棵b+树互不干扰

  1. mysql联合索引的最左前缀原理

MySQL的联合索引(也称为复合索引或多列索引)的最左前缀原理是指,当MySQL使用联合索引进行查询时,它会从联合索引的最左列开始匹配查询条件。只有当查询条件使用了联合索引的最左侧列时,索引才会被有效利用。

具体来说,假设我们有一个联合索引 (a, b, c),这个索引是按照列 a、b 和 c 的顺序进行排序的。现在,我们考虑以下几种查询情况:

单独查询列a:在这种情况下,查询条件使用了联合索引的最左列(列a),因此MySQL可以直接使用这个索引来快速定位到匹配的行。

查询列a和列b:查询条件同时包含了列a和列b,这也满足了最左前缀原则,因为列a是联合索引的最左列。MySQL会利用这个联合索引来加速查询。

查询列a、列b和列c:当查询条件包含了联合索引的所有列(列a、列b和列c)时,索引的使用效率是最高的,因为所有列都被用来定位数据。

然而,以下情况则不会有效地利用联合索引 (a, b, c):

只查询列b或列c:由于列b和列c不是联合索引的最左列,因此当查询条件只包含这些列时,联合索引不会被使用。MySQL会进行全表扫描或者尝试使用其他可能的索引(如果有的话)。

最左前缀原则也适用于索引的列顺序。如果你有一个 (b, a, c) 的联合索引,那么它对于基于列 b 的查询是高效的,但对于基于列 a 的查询则不是,因为列 a 不是这个联合索引的最左列。

  1. 为什么不等于条件会导致索引失效

一般情况,“!=”操作会选择表中绝大部分数据(假设一个字段有10个可选值,平均分布,不等操作就会选出其中90%的数据),第2步操作时间和全表扫描时间差不多(看数据量来说9:10)。然而还需要加上第一步读取索引的时间。这样一来,所花的总时间就比不走索引还多。

对于这种情况,数据库当然选择不走索引了。

不过,不等于不能使用索引这个说法是不准确的。

select col1,count(*) from table where col1!=’xxxx’ 就可以走col1的索引。 select col1, col2 from table where col1!=’xxxx’ 可以走col1,col2上的复合索引

什么情况下,”!=” 和 “not in”可以走索引?

直接读取索引,不用读取表里的数据,就可以获取所需数据的SQL,”!=”和”not in”也可以走索引

换个说法是,满足Covering Index使用条件的SQL,”!=”和”not in”也可以走索引

  1. IO多路复用

IO多路复用技术(select函数模型和poll函数模型):进程通过告诉多路复用器(内核)(也就是select函数和poll函数)所有的socket号,多路复用器再去获取每一个socket的状态,当程序获取到某个socket号有事件发生了,则去该socket号上进行处理对应的事件,read事件或者是recived事件。(补充select函数与poll函数的区别是,前者底层是数组,所以有最大连接数的限制,后者是链表,无最大连接数的限制)

缺点:①同样与NIO相同,需要遍历所有socket,O(N)复杂度。②重复传递数据。因为内核是无状态的,每次都要根据进程不断重复从用户态向内核态传递所有的socket号去遍历每一个socket,获取它们的状态。浪费资源与效率,可以使用一个记事本记录每个socket的监听事件。

IO多路复用技术(epoll函数模型):epoll函数模型主要是调用了三个函数:epoll_create() , epoll_ctl() , epoll_wait();

底层流程:①通过epoll_create() 函数创建一个文件,返回一个文件描述符(Linus系统一切对象皆为文件)fd ② 创建socket接口号4,绑定socket号与端口号,监听事件,标记为非阻塞。通过epoll_ctl() 函数将该socket号 以及 需要监听的事件(如listen事件)写入fd中。③循环调用epoll_wait() 函数进行监听,返回已经就绪事件序列的长度(返回0则说明无状态,大于0则说明有n个事件已就绪)。例如如果有客户端进行连接,则再调用accept()函数与4号socket进行连接,连接后返回一个新的socket号,且需要监听读事件,则再通过epoll_ctl()将新的socket号以及对应的事件(如read读事件)写入fd中,epoll_wait()进行监听。循环往复。

优点:不需要再遍历所有的socket号来获取每一个socket的状态,只需要管理活跃的连接。即监听在通过epoll_create()创建的文件中注册的socket号以及对应的事件。只有产生就绪事件,才会处理,所以操作都是有效的,为O(1).

美团优选

  1. 线程池参数

ThreadPoolExecutor 3 个最重要的参数:

  • corePoolSize : 任务队列未达到队列容量时,最大可以同时运行的线程数量。(核心线程数量)
  • maximumPoolSize : 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。(线程的最大线程数)
  • workQueue: 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数 :

  • keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁。
  • unit : keepAliveTime 参数的时间单位。
  • threadFactory :executor 创建新线程的时候会用到。(线程工厂)
  • handler :拒绝策略
  1. 封装,继承,多态的底层原理和好处

  • 封装

封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空调制冷,空调本身就没有意义了

  • 继承

不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

  • 多态

多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

对象类型和引用类型之间具有继承(类)/实现(接口)的关系;

引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;

多态不能调用“只在子类存在但在父类不存在”的方法;

如果子类重写了父类的方法,真正执行的是子类重写的方法,如果子类没有重写父类的方法,执行的是父类的方法。

  1. java的泛型

Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。

  1. 深浅拷贝

浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。

深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

  1. equals和==

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

  1. redis底层

  2. 缓存三兄弟以及解决方案

  3. MQ

  4. SpringIOC以及循环依赖和注解失效的场景

详解

  1. Mybatis

MyBatis的基本工作原理就是:先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结果封装成Java类。

  1. MySql的b+树和最左匹配原则

  2. ES有了解过吗

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。

使用场景

1、为用户提供按关键字查询的全文搜索功能。 2、实现企业海量数据的处理分析的解决方案。大数据领域的重要一份子,如著名的ELK框架(ElasticSearch,Logstash,Kibana),。

特点:

天然分片,天然集群

es 把数据分成多个shard,下图中的P0-P2,多个shard可以组成一份完整的数据,这些shard可以分布在集群中的各个机器节点中。随着数据的不断增加,集群可以增加多个分片,把多个分片放到多个机子上,已达到负载均衡,横向扩展。

在实际运算过程中,每个查询任务提交到某一个节点,该节点必须负责将数据进行整理汇聚,再返回给客户端,也就是一个简单的节点上进行Map计算,在一个固定的节点上进行Reduces得到最终结果向客户端返回。

天然索引

ES 所有数据都是默认进行索引的,这点和mysql正好相反,mysql是默认不加索引,要加索引必须特别说明,ES只有不加索引才需要说明。

而ES使用的是倒排索引和Mysql的B+Tree索引不同

滴滴日常实习

  1. 线程池参数

核心线程数corePoolSize,线程的最大线程数maximumPoolSize,当线程数达到最大线程数时,新加进来的线程会加在workQueue。。。

  1. Java的集合

List,Map,Queue,Set

详解

  1. HashMap扩容机制

当数组长度达到阈值时(默认为0.75),需要进行扩容操作,将数组长度扩大为原来的两倍,并将原有元素重新分布到新数组中。

  1. 链表转化为红黑树的机制

    第一次添加元素的时候,默认初期长度为16,当往map中继续添加元素的时候,通过hash值跟数组长度取“与”来决定放在数组的哪个位置,如果出现放在同一个位置的时候,优先以链表的形式存放,在同一个位置的个数又达到了8个(代码是>=7,从0开始即第8个开始判断是否转化成红黑树),如果数组的长度还小于64的时候,则会扩容数组。如果数组的长度大于等于64的话,才会将该节点的链表转换成树。在扩容完成之后,如果某个节点的是树,同时现在该节点的个数又小于等于6个了,则会将该树转为链表。

  2. hashMap是线程安全的还是线程不安全的

不安全的

  1. hashMap为什么不安全

1.并发修改异常:如果多个线程同时尝试修改HashMap的状态,而没有适当的同步措施,就可能导

ConcurrentModificationException

2.数据不一致:在没有同步的情况下,一个线程可能会看到另一个线程正在修改的中间状态,导致

读取到的数据不一致。

3.死循环:HashMap在扩容时会重新计算哈希值并重新分配元素到新的桶中。如果多个线程同时进

行扩容操作,可能会导致死循环。

4.快速失败迭代器:HashMap的迭代器是快速失败的,意味着如果检测到HashMap在迭代过程中被

修改,迭代器会立即抛出ConcurrentModificationException.。在多线程环境中,这种修改很可能是由另一个线程引起的。

  1. 线程安全应该用哪一个

ConcurrentHashMap

  1. 异常的分类

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • **Error**:Error 属于程序无法处理的错误 ,不建议通过catch捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundExceptionSQLException…。

Unchecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):

  • NullPointerException(空指针错误)
  • IllegalArgumentException(参数错误比如方法入参类型错误)
  • NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ClassCastException(类型转换错误)
  • ArithmeticException(算术错误)
  • SecurityException (安全错误比如权限不够)
  • UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)
  1. 项目中有没有用到反射

  2. spring中哪里用到反射

Spring 框架中广泛使用了反射机制,它在多个方面都有应用,以下是一些主要的使用场景:

  1. 依赖注入(DI)

    1. Spring 使用反射来实现依赖注入。它通过扫描类定义中的注解(如 @Autowired),动态地创建对象并注入依赖。
  2. AOP(面向切面编程)

    1. 反射用于创建代理对象,这是 Spring AOP 实现方法拦截和增强的基础。
  3. Bean 工厂

    1. Spring 的 Bean 工厂使用反射来实例化、配置和组装 Bean。它通过反射获取 Bean 类的构造函数、字段和方法信息。
  4. 注解驱动

    1. 反射用于处理 Java 5 引入的注解,Spring 利用注解来简化配置,如 @Component, @Service, @Repository, @Controller 等。
  5. 类型转换

    1. Spring 的类型转换服务使用反射来转换属性值,例如,将字符串转换为特定的类型。
  6. 资源访问

    1. Spring 利用反射来访问类路径下的资源文件,如加载配置文件。
  7. 事件发布

    1. 事件发布机制中,Spring 使用反射来调用事件监听器方法。
  8. 数据访问

    1. 在数据访问层,如 Hibernate 或 JPA,Spring 使用反射来处理实体类的元数据。
  9. Spring MVC

    1. 在 Spring MVC 中,反射用于处理控制器方法的参数绑定,以及调用处理方法。
  10. 动态代理

    1. Spring 为实现事务管理和其他 AOP 功能,使用动态代理来创建代理对象。
  11. JMX 集成

    1. Spring 允许将 Spring 管理的 Bean 暴露为 JMX MBeans,这需要使用反射来访问和管理 Bean 的属性和方法。
  12. 测试支持

    1. Spring 提供的测试框架(如 Spring TestContext Framework)使用反射来设置测试环境和执行测试。
  13. Java中有哪些锁

img

详解

  1. 对方法和代码块加锁应该用哪一个

sychronized

  1. AQS原理,构建用了哪些锁

详解 CLH队列锁

CLH(Craig,Landin,and Hagersten) 队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。在 CLH 同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)

AQS 使用 int 成员变量 state 表示同步状态,通过内置的 线程等待队列 来完成获取资源线程的排队工作。

  1. threadLocal原理

最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是**ThreadLocalMap**的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

每个**Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal**为 key ,Object 对象为 value 的键值对。

原理

  1. AOP原理

详解

  1. 注入Bean用哪些注解

  2. 构造函数注入

    1. 使用@Autowired在构造函数上进行注入。
  3. Setter方法注入

    1. 使用@Autowired在Setter方法上进行注入。
  4. 字段注入

    1. 使用@Autowired直接在字段上进行注入。
  5. JSR-330注入

    1. 使用@Inject进行注入,这是JSR-330标准的一部分。
  6. 资源注入

    1. 使用@Resource进行注入,这同样是Java EE的一部分,可以通过名称进行注入。
  7. 指定注入

    1. 使用@Qualifier@Autowired@Inject结合使用,用于解决同名Bean的注入问题。
  8. 条件注入

    1. 使用@Profile指定在哪个环境下注入特定的Bean。
  9. 延迟注入

    1. 使用@Lazy@Autowired结合使用,实现延迟加载Bean。
  10. 配置注入

    1. 使用@Configuration类结合@Bean注解,用于配置Bean。
  11. 属性注入

    1. 使用@Value注解注入配置文件中的属性值。
  12. 方法注入

    1. 使用@Lookup进行方法级别的注入,这是JSR-330的一部分。
  13. 优先级注入

    1. 使用@Primary指定当存在多个候选Bean时优先注入的Bean。
  14. cglib动态代理的原理

详解

  1. JDK动态代理为什么只能代理有接口的类

是因为JDK动态代理本身机制决定的,首先在java里面动态代理是Proxy.newProxyInstance()这个方法来实现的,它需要传入被动态代理的一个接口类,还是取决于JDK动态代理的的一个底层实现,JDK动态代理会在程序运行期间,去动态生产一个代理类,叫$Proxy0,那么这个动态生成的代理类会去继承一个java.lang.reflect.Proxy这样一个类,同时还会去实现被代理类的接口,在java里面不支持多种继承的,而每个动态代理都继承一个Proxy,所以就导致的JDK里面的动态代理只能代理接口,而不能代理实现类,cglib实现动态代理的逻辑是使用子类继承代理类,就没有单继承的限制了。

JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

  1. MVC是指哪三层

  2. Model(模型)

    1. 模型层代表数据和业务逻辑。它负责数据的存储、检索和处理,以及执行应用程序的业务规则。
  3. View(视图)

    1. 视图层负责呈现数据(即模型)给用户。它通常是用户界面的一部分,显示数据并允许用户与应用程序交互。
  4. Controller(控制器)

    1. 控制器层接收用户的输入并调用模型和视图去完成用户的请求。它充当模型和视图之间的中介,解释用户的输入,并将其转化为模型状态的改变,然后选择合适的视图来显示。

在Web应用程序中,MVC模式通常被用来构建服务器端的应用程序,其中:

  • Model 可以是JavaBeans或具有业务逻辑的POJO(Plain Old Java Object)。
  • View 可以是JSP、HTML页面或任何其他模板引擎生成的输出。
  • Controller 可以是Servlet、Struts Action或Spring MVC Controller
  1. 实现这三层分别用哪些注解

Model(模型层):

模型层通常不需要特定的Spring MVC注解,因为它主要包含业务逻辑和数据访问代码。但是,可以使用以下几个注解:

  • @Entity:用于标识一个实体类,通常与JPA(Java Persistence API)一起使用。
  • @Table:用于指定实体类对应的数据库表。
  • @Id:用于标识实体类中的主键字段。
  • @Column:用于映射实体类的字段到数据库表的列。
  • @ManyToOne@OneToMany@OneToOne@ManyToMany:用于标识实体类之间的关联关系。

View(视图层):

视图层在Spring MVC中通常由JSP、HTML页面或Thymeleaf等模板引擎来实现。视图层本身不使用Spring特定的注解,但是可以通过以下方式与Spring MVC集成:

  • 使用JSP标签库:例如<spring:bind path="..." />,用于数据绑定和显示错误信息。
  • 使用模板引擎(如Thymeleaf)的特定注解或语法。

Controller(控制器层):

控制器层是Spring MVC的核心,用于处理用户请求和响应。以下是一些常用的注解:

  • @Controller:用于标识一个类为控制器。
  • @RestController:用于标识一个类为控制器,并且所有的方法返回值都是JSON格式,它组合了@Controller@ResponseBody
  • @RequestMapping:用于映射HTTP请求到控制器的处理方法上,可以标注在类或方法上。
  • @GetMapping@PostMapping@PutMapping@DeleteMapping:用于简化@RequestMapping的配置,分别对应HTTP的GET、POST、PUT、DELETE请求。
  • @Autowired:用于自动装配Spring容器中的Bean。
  • @PathVariable:用于从URL路径中提取变量。
  • @RequestParam:用于从请求参数中提取数据。
  • @RequestBody:用于读取请求的主体内容,通常用于接收JSON或XML格式的请求数据。
  • @ResponseBody:用于指示方法的返回值应该直接作为响应的正文返回,而不是返回一个视图(View)。
  • @ExceptionHandler:用于全局异常处理。
  • @ControllerAdvice:用于定义全局的控制器增强功能,如异常处理、响应构建等。
  1. 取一个自己设置好的变量用哪个注解

@Value,@Autowired,@Resource,@Inject

  1. 自动装配用哪个注解

@Autowired:按属性进行注入,如果有多个属性相同的Bean,再按照Bean的id注入;可以通过@Qualifier(“id”) 指定id;可以设置@Primary 为首选Bean,可以指定@Autowired(required = false) 找不到Bean不强行注入;

@Resource:默认按组件名称进行装配的;不支持@Primary功能,不支持@Autowired(reqiured=false);

@Inject:需要导入javax.inject的包,和Autowired的功能一样支持@Primary注解; 没有required=false的功能;

  1. MySQL事务隔离级别

读未提交,读已提交,可重复读,串行化

  1. MySQL索引分类

  2. 单列索引 vs 复合索引

    1. 单列索引:索引只包含单个列。
    2. 复合索引(也称为多列索引或组合索引):索引包含两个或更多列。
  3. 聚簇索引 vs 非聚簇索引

    1. 聚簇索引(Clustered Index):表中的数据行根据索引列的顺序进行物理排序和存储。在InnoDB存储引擎中,主键默认是一个聚簇索引。
    2. 非聚簇索引(Non-Clustered Index):索引结构和数据存储是分开的,索引列可以不是表中的主键。大多数索引类型都是非聚簇索引。
  4. 唯一索引 vs 非唯一索引

    1. 唯一索引:索引列的值必须唯一,但可以有空值。
    2. 非唯一索引:索引列的值可以有重复。
  5. 主键索引 vs 非主键索引

    1. 主键索引:表中主键列上的索引,必须是唯一索引,不允许有空值。
    2. 非主键索引:除了主键索引之外的所有索引。
  6. 全文索引

    1. 用于对文本数据进行全文搜索,可以提高搜索效率。
  7. 空间索引

    1. 用于地理空间数据类型,支持空间数据的查询。
  8. 索引视图

    1. 一种特殊的索引,它实际上是一个存储在索引中的视图。
  9. 覆盖索引

    1. 当一个查询只需要访问索引中的列,而不需要访问表中的数据行时,这个索引被称为覆盖索引。
  10. 外部索引

    1. 由外部存储引擎管理的索引,不是由MySQL服务器直接管理。
  11. 隐藏索引

    1. MySQL 5.7及以上版本支持隐藏索引,它们对优化器是透明的,不会自动用于查询优化。
  12. 普通索引

    1. 这是最基本的索引类型,没有附加的约束或特性。
  13. 索引分区

    1. 将索引分成多个部分,每个部分包含表的一部分数据,以提高查询性能。
  14. 对多个字段加索引用什么索引

联合索引

  1. redis内存淘汰策略

详解

Redis 提供了 6 种内存淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  4. allkeys-lru(least recently used):从数据集(server.db[i].dict)中移除最近最少使用的数据淘汰。
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  6. no-eviction(默认内存淘汰策略):禁止驱逐数据,当内存不足以容纳新写入数据时,新写入操作会报错。

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。
  2. allkeys-lfu(least frequently used):从数据集(server.db[i].dict)中移除最不经常使用的数据淘汰。

淘天java

  1. 介绍java异常体系

  2. sychronized底层原理,锁升级过程

原理详解

  1. 分库分表介绍,什么时候做分库,mysql的连接数跟什么有关系

详解

MySQL的连接数是指能够同时与MySQL数据库建立连接的客户端数量。MySQL的连接数可以通过配置文件进行设置,具体取决于MySQL版本、硬件资源、操作系统和使用场景等因素。默认情况下MySQL的最大连接数为100,但可以通过修改配置文件中的max_connections参数来增加连接数。MySQL底层实现连接数的原理是,对于每个连接,MySQL会为其分配一定的内存空间,包括连接缓存、语句缓存、结果缓存等。连接数的增加会占用更多的内存资源,如果同时有大量的连接请求,可能会导致内存不足而影响性能。因此,在设置连接数时,需要综合考虑硬件资源和应用场景,以避免连接数过多导致系统负载过高。另外,MySQL还采用了连接池技术来优化连接管理。连接池是一种管理连接的机制,它通过在启动时预先创建一定数量的连接,以满足后续连接请求的需要。当连接请求结束后,连接不会被关闭,而是重新放回到连接池中,供下一次请求使用。这样可以减少连接的创建和销毁过程,提高系统性能和可靠性。

  1. http和https的区别

  2. mvcc是怎么实现的,什么是幻读,怎么解决幻读,select for update加的是什么锁

MVCC 的实现依赖于:隐藏字段、Read View、undo log。详解

幻读它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

InnoDB存储引擎在 RR 级别下通过 MVCCNext-key Lock 来解决幻读问题:

1、执行普通 **select**,此时会以 MVCC 快照读的方式读取数据

在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 Read View 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”

2、执行 select…for update/lock in share mode、insert、update、delete 等当前读

在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!InnoDB 使用 Next-key Lock来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读

  1. @Transational注解括号里面有哪些常见的参数

@Transactional 注解是 Spring 框架中用于声明事务管理的方法。它通常用于声明在方法执行期间需要进行事务管理。以下是一些常见的参数:

  1. value: 指定使用的事务管理器名称。如果项目中配置了多个事务管理器,可以使用此参数指定使用哪一个。

  2. propagation: 事务的传播行为。常见的值包括:

    1. Propagation.REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    2. Propagation.REQUIRES_NEW:总是创建一个新的事务,如果当前存在事务,则挂起当前事务。
    3. Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    4. Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
    5. Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务。
    6. Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  3. isolation: 事务的隔离级别。常见的值包括:

    1. Isolation.DEFAULT:使用后端数据库的默认隔离级别。
    2. Isolation.READ_UNCOMMITTED:读未提交。
    3. Isolation.READ_COMMITTED:读已提交。
    4. Isolation.REPEATABLE_READ:可重复读。
    5. Isolation.SERIALIZABLE:串行化。
  4. timeout: 事务的超时时间,单位为秒。如果事务在这个时间内没有完成,则自动回滚。

  5. readOnly: 指定事务是否为只读事务。只读事务可以提供一些性能优化。

  6. rollbackFor: 指定在遇到哪些异常时事务应该回滚。可以指定一个异常类或者异常类的数组。

  7. noRollbackFor: 指定在遇到哪些异常时事务不应该回滚。可以指定一个异常类或者异常类的数组。

  8. 介绍一下zset

  9. 有哪些常用的线程池,Excutors里的线程池有哪些问题

FixedThreadPool:固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。

SingleThreadExecutor: 只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。

CachedThreadPool: 可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。

ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池。

FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。

CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

  1. 介绍一下jdk1.8的jvm内存区域

img

  1. 介绍一下双亲委派机制

  2. 介绍一下CAP理论,为什么能同时满足两个

详解

CAP 也就是 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性) 这三个单词首字母组合。

CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。

  1. ThreadLocal介绍一下

  2. Wait sleep的区别

共同点:两者都可以暂停线程的执行。

区别

  • sleep() 方法没有释放锁,而 wait() 方法释放了锁
  • wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。
  • wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
  • sleep()Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。
  1. 二叉树中序遍历的时间复杂度

O(n)

  1. xms和xss能干什么

详解

高德一面

  1. 创建对象的方式

创建对象方式是否调用了构造器
new关键字
Class.newInstance
Constructor.newInstance
Clone
反序列化

详解

  1. 类加载的过程

加载-连接(验证、准备和解析)-初始化

  1. 类加载发生几次

一次,双亲委派机制

  1. OOM

详解

  1. threadLocal使用场景,基本原理,有什么坑

  2. 内存泄漏为什么要手动remove

  3. thread怎么配合线程池使用

  4. JWT组成

详解

  1. 讲讲TOKEN

详解

袋鼠云Java实习面经

  1. int类型占多少位

32位

  1. JVM内存结构?方法区里存的是什么?方法区是怎么实现的?

JVM内存结构分为线程私有和线程共享两类,线程共享的有堆,方法区,直接内存,线程私有的有虚拟机栈,本地方法栈,程序计数器。

方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

jdk1.7及以前,方法区的实现是永久代,jdk1.8及以后是元空间

  1. GC算法

复制算法、标记清除算法、标记整理算法和分代收集算法

  1. 类加载的过程

加载-连接(验证,准备,解析)-初始化

类加载过程的第一步,主要完成下面 3 件事情:

  1. 通过全类名获取定义此类的二进制字节流。
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。

验证阶段主要由四个检验阶段组成:

  1. 文件格式验证(Class 文件格式检查)
  2. 元数据验证(字节码语义检查)
  3. 字节码验证(程序语义检查)
  4. 符号引用验证(类的正确性检查)

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化阶段是执行初始化方法()方法的过程,是类加载的最后一步,这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)。

  1. 线程池的创建方式

方式一:通过ThreadPoolExecutor构造函数来创建(推荐)。

方式二:通过 Executor 框架的工具类 Executors 来创建。

  1. 有哪些参数

核心参数有以下三个

corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。

maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

workQueue****: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

  1. 什么是线程安全

线程安全和不安全是在多线程环境下对于同一份数据的访问是否能够保证其正确性和一致性的描述。

  • 线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
  • 线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。
  1. CAS

详解

CAS 即比较并替换(Compare And Swap),是实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS 是一条 CPU 的原子指令(cmpxchg 指令),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法(如 compareAndSwapXXX)底层实现即为 CPU 指令 cmpxchg

  1. AQS

详解

AQS 就是一个抽象类,主要用来构建锁和同步器。

AQS 为构建锁和同步器提供了一些通用功能的实现,因此,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLockSemaphore,其他的诸如 ReentrantReadWriteLockSynchronousQueue等等皆是基于 AQS 的。

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是基于 CLH 锁 (Craig, Landin, and Hagersten locks) 实现的。

AQS 使用 int 成员变量 state 表示同步状态,通过内置的 FIFO 线程等待/等待队列 来完成获取资源线程的排队工作。

state 变量由 volatile 修饰,用于展示当前临界资源的获锁情况。

img

  1. ABA问题了解过吗

如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 “ABA”问题。

ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。JDK 1.5 以后的 AtomicStampedReference 类就是用来解决 ABA 问题的,其中的 compareAndSet() 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

  1. SPI知道吗

详解

  1. ThreadLocal有什么用,底层原理知道吗

  2. Join()是做什么的

连接查询

  1. 索引有哪些种类

主键索引,聚簇索引,非聚簇索引,单列索引,联合索引,非主键索引,唯一索引,非唯一索引,

全文索引,索引视图

  1. 事务的四大特性

A:原子性:指的是一个事务是一个完整的操作,要么全部成功,要么全部失败

C:一致性: 意思是事务在操作前后,数据是保持一致的,比如转账操作,转账业务双方在事务前后的总钱数是不变得

I:隔离性: 多个事务在执行的时候,是互不干扰的,一个事务的执行不会影响另一个事务。

D:持久性:指的是,事务一旦提交,对数据库的操作是永久的

  1. MySQL主从同步,从库是怎么存数据的

主从复制

  • 主库将数据库中数据的变化写入到 binlog
  • 从库连接主库
  • 从库会创建一个 I/O 线程向主库请求更新的 binlog
  • 主库会创建一个 binlog dump 线程来发送 binlog ,从库中的 I/O 线程负责接收
  • 从库的 I/O 线程将接收的 binlog 写入到 relay log 中。
  • 从库的 SQL 线程读取 relay log 同步数据到本地(也就是再执行一遍 SQL )。

详解

  1. mvcc底层

底层是基于隐藏字段,readview,undolog实现

  1. 回表是什么

在MySQL数据库中,回表(Look Up)指的是在进行索引查询时,首先通过索引定位到对应页,然后再根据行的物理地址找到所需的数据行。换句话说,回表是指根据索引查询到的主键值再去访问主键索引,从而获取完整的数据记录。

什么情况下会触发回表?

  • 索引不Cover所有需要查询的字段
  • 使用了非聚簇索引
  • 使用了覆盖索引但超过了最大索引长度

哪些情况下不会触发回表?

覆盖索引

使用聚簇索引

  1. 创建索引有什么需要注意的点

索引不是创建地越多越好,因为维护索引也是需要代价的

非必要的列没必要创建索引,比如说性别,如果男和女比例是1:1,那就没必要创建

注意一些索引实现的场景,创建联合索引的时候注意最左前缀原则

最好在占用内存较少的字段上建立索引

  1. TCP和UDP的区别

是否面向连接:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。

是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。

是否有状态:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(这很渣男!)。

传输效率:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。

传输形式:TCP 是面向字节流的,UDP 是面向报文的。

首部开销:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。

是否提供广播或多播服务:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;

  1. HTTPS是如何保证安全的

HTTPS 协议(Hyper Text Transfer Protocol Secure),是 HTTP 的加强安全版本。HTTPS 是基于 HTTP 的,也是用 TCP 作为底层协议,并额外使用 SSL/TLS 协议用作加密和安全认证。

img

端口号:HTTP 默认是 80,HTTPS 默认是 443。

URL 前缀:HTTP 的 URL 前缀是 http://,HTTPS 的 URL 前缀是 https://

安全性和资源消耗:HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。

  1. 网络IO模型了解吗,说说IO多路复用

Redis+MySql 高频 java面试

  1. redis作为缓存,mysql的数据如何与redis进行同步呢(双写一致性)

回答时要先介绍业务背景1. 一致性要求较高 2. 允许延迟一致

  1. 如果是把文章的热点数据存入到了缓存中,虽然是热点数据,但是实时要求性并没有那么高,所以,可以采用异步的方案同步的数据

允许延时一致的业务,采用异步通知

  • 使用MQ中间中间件,更新数据之后,通知缓存删除
  • 利用canal中间件,不需要修改业务代码,伪装为mysql的一个从节点,canali通过读取binlog数据更新缓存
  1. 如果库存存入到了缓存中,这个需要实时的进行数据同步,为了保证数据的强一致,就需要采用redisson提供的读写锁来保证数据的同步

强一致性的,采用Redisson提供的读写锁

  • 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作
  • 排他锁:独占锁writeLock也叫,加锁之后,阻塞其他线程读写操作

听说过延时双删吗?为什么不用它呢?

​ 延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除

缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一

致性,所以没有采用它。

  1. redis作为缓存, 数据的持久化是怎么做的

在Redis中提供了两种数据持久化的方式:1.RDB 2.AOF

RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。

AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据

的时候,会从这个文件中再次执行一遍命令来恢复数据

RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我通常在项目中会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略。

  1. redis的数据过期策略

Redⅰs对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略)

redis里面提供了两种过期策略:惰性删除、定期删除

惰性删除:设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key

优点:对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的ky不用浪费时间进行过期检查

缺点:对内存不友好,如果一个key已经过期,但是一直没有使用,那么该ky就会一直存在内存中,内存永远不会释放

定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key)。

定期清理有两种模式:

SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis..conf的hz选项来调整这个次数

FAST模式执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms

优点:可以通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。另外定期删除,也能有效释放过期键占用的内存。

缺点:难以确定删除操作执行的时长和频率。

Redis的过期删除策略:惰性删除+定期删除两种策略进行配合使用

  1. redis的数据淘汰策略

数据的淘汰策略:当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。

Redis支持8种不同策略来选择要删除的key:

  1. noeviction:不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。
  2. volatile-ttl:对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰
  3. allkeys-random:对全体key,随机进行淘汰。
  4. volatile-random:对设置了TTL的key,随机进行淘汰。
  5. allkeys–lru:对全体key,基于LRU算法进行淘汰
  6. volatile-lru:对设置了TTL的key,基于LRU算法进行淘汰
  7. allkeys-lfu:对全体key,基于LFU算法进行淘汰
  8. volatile-lfu:对设置了TTL的key,基于LFU算法进行淘汰

img

优先使用allkeys-lru策略。充分利用LRU算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。

如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用allkeys-random,随机选择淘汰。

如果业务中有置顶的需求,可以使用volatile-lru策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,会淘汰其他设置过期时间的数据。

如果业务中有短时高频访问的数据,可以使用allkeys-lfu或volatile-lfu策略。

扩展问题:

1.数据库有1000万数据,Redis.只能缓存20w数据,如何保证Redis中的数据都是热点数据?

使用allkeys-lru(挑选最近最少使用的数据淘汰)淘汰策略,留下来的都是经常访问的热点数据

2.Redis的内存用完了会发生什么?

主要看数据淘汰策略是什么?如果是默认的配置(noeviction),会直接报错

  1. redis分布式锁时如何实现的

在redis中提供了一个命令setnx(SET if not exists)由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候是其他客户端是不能设置这个key的redis的setnx指令不好控制Redis实现分布式锁有效时长,我当时采用的redis的一个框架redisson实现的。在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自选不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。

Redisson:实现分布式锁如何合理的控制锁的有效时长?

在redisson的分布式锁中,提供了一个WatchDog(看门狗),一个线程获取锁成功以后,

WatchDog会给持有锁的线程续期(默认是每隔10秒续期一次)

Redisson的这个锁,可以重入吗?

可以重入,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用的hash结构

来存储线程信息和重入的次数

Redisson锁能解决主从数据一致的问题吗?

不能解决,但是可以使用redisson提供的红锁来解决,但是这样的话,性能就太低了,如果

业务中非要保证数据的强一致性,建议采用zookeeper3实现的分布式锁

  1. Redis集群有哪些方案

主从复制(主从同步)

介绍一下主从同步

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据

主从同步数据的流程

全量同步:

  1. 从节点请求主节点同步数据(replication id、offset)
  2. 主节点判断是否是第一次请求,是第一次就与从节点同步版本信息(replication id和offset)
  3. 主节点执行bgsave,生成rdb文件后,发送给从节点去执行
  4. 在rdb生成执行期间,主节点会以命令的方式记录到缓冲区(一个日志文件)
  5. 把生成之后的命令日志文件发 送给从节点进行同步

增量同步:

  1. 从节点请求主节点同步数据,主节点判断不是第一次请求,不是第一次就获取从节点的offset值
  2. 主节点从命令日志中获取offset值之后的数据发送给从节点进行数据同步

哨兵模式

怎么保证Redis的高并发高可用

​ 首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果masteri故障,Sentinel会将一

个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服

务发现来源,当集群发生故障转移时,会将最新信息推送给Rdis的客户端,所以一般项目都会采用哨

兵的模式来保证redis的高并发高可用

你们使用redis是单点还是集群,哪种集群

​ 我们当时使用的是主从(1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务

redis集群脑裂,该怎么解决呢?

这个在项目很少见,不过脑裂的问题是这样的,我们现在用的是reds的哨兵模式集群的有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redis master节点和redis slave节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old masterl降为slave,这时再从新的master同步数据,这会导致old master中的大量数据丢失。

关于解决的话,我记得在redis的配置中可以设置:第一可以设置最少的slave节点个数,比如设置至少

要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝

请求,就可以避免大量的数据丢失

分片集群

redis的分片集群有什么作用

  • 集群中有多个master,每个master保存不同数据
  • 每个master都可以有多个slave节点
  • master之间通过ping监测彼此健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

Redis分片集群中数据是怎么存储和读取的?

  • Redis分片集群引入了哈希槽的概念,Redis集群有16384个哈希槽
  • 将16384个插槽分配到不同的实例
  • 读写数据:根据key的有效部分计算哈希值,对16384取余(有效部分,如果ky前面有大括号,大括号的内容就是有效部分,如果没有,则以ky本身做为有效部分)余数做为插槽,寻找插槽所在的实例
  1. MySQL如何定位慢查询

当压测的时候有的接口非常的慢,接口的响应时间超过了2秒以上,如果系统部署了运维的监控系统Skywalking,在展示的报表中可以看到是哪一个接口比较慢,并且可以分析这个接口哪部分比较慢,这里可以看到SQL的具体的执行时间,所以可以定位是哪个sql出了问题

如果,项目中没有这种运维的监控系统,其实在MySQL中也提供了慢日志查询的功能,可以在MySQL

的系统配置文件中开启这个慢日志的功能,并且也可以设置SQL执行超过多少时间来记录到一个日志文

件中。

  1. sql语句执行的很慢,该如何分析

​ 如果一条sql执行很慢的话,我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况,比如在这里面可以通过key和key_len检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况,第二个,可以通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描,第三个可以通过extra建议来判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复

  1. 聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
  1. 知道什么叫覆盖索引吗

覆盖索引是指查询使用了索引,返回的列,必须在索引中全部能够找到

使用id查询,直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。

如果返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select*

  1. 索引创建的原则

  2. 针对于数据量较大,且查询比较频繁的表建立索引。单表超过10万数据(增加用户体验)

  3. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。

  4. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。

  5. 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。

  6. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。

  7. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。

  8. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

  9. 什么情况下索引会失效

常见的五种:

  • 违反最左前缀法则
  • 范围查询右边的列,不能使用索引
  • 不要在索引列上进行运算操作,索引将失效
  • 字符串不加单引号,造成索引失效。(类型转换)
  • 以%开头的Like模糊查询,索引失效
  1. 并发事务问题

脏读:一个事务读到另外一个事务还没有提交的数据。

不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。

幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了”幻影”。

  1. 解释一下mvcc

MySQL中的多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突

  • 隐藏字段:

①trx_id(事务id),记录每一次操作的事务id,是自增的

②roll_pointer(回滚指针),指向上一个版本的事务版本记录地址

  • undo log:

①回滚日志,存储老版本数据

②版本链:多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表

  • readView解决的是一个事务查询选择版本的问题

根据readView的匹配规则和当前的一些事务id判断该访问那个版本的数据

不同的隔离级别快照读是不一样的,最终的访问的结果不一样

RC:每一次执行快照读时生成ReadView

RR: 仅在事务中第一次执行快照读时生成ReadView,后续复用

Spring面试题

  1. Spring框架中的单例bean是线程安全的吗

不是线程安全的,是这样的

当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。

Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。

比如:我们通常在项目中使用的Spring bean都是不可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。

如果你的bean有多种状态的话(比如View Model>对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由”singleton”变更为”prototype”。

因为一般在spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决

  1. 什么是AOP

面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,实现方式主要有动态代理,和字节码操作等方式

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

  1. 项目中有没有使用到AOP

记录操作日志,缓存,spring实现的事务

记录日志:

核心是:使用aop中的环绕通知+切点表达式(找到要记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库

  1. Spring中的事务是如何实现的

Spring支持编程式事务管理和声明式事务管理两种方式。

  • 编程式事务控制:需使用TransactionTemplate:来进行实现,对业务代码有侵入性,项目中很少使用
  • 声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

其本质是通过AOP功能,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

  1. Spring事务失效的场景以及解决方法

异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出

抛出检查异常,配置rollbackFor属性为Exception.class

非public方法导致的事务失效,改为public

  1. Spring的bean的生命周期

  2. 通过BeanDefinition获取bean的定义信息

  3. 调用构造函数实例化bean

  4. bean的依赖注入

  5. 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)

  6. Bean的后置处理器BeanPostProcessor–前置

  7. 初始化方法(InitializingBean、init-method)

  8. Bean的后置处理器BeanPostProcessor-后置

  9. 销毁bean

  10. Spring的循环依赖问题

循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bea互相特有对方,最终形成闭环。比如A依赖于B,B依赖于A

循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖

一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

二级缓存:缓存早期的bean对象(生命周期还没走完)

三级缓存:缓存的是ObjectFactory,.表示对象工厂,用来创建某个对象的

简单介绍一下流程

  1. 构造方法出现了循环依赖怎么解决呢

A依赖于B,B依赖于A,注入的方式是构造函数

原因:由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入

解决方案:使用@Lazy进行獭加载,什么时候需要对象再进行bean对象的创建

  1. SpringMVC的执行流程

视图版本JSP流程

  1. 用户发送出请求到前端控制器DispatcherServlet
  2. DispatcherServletl收到请求调用HandlerMapping(处理器映射器)
  3. HandlerMapping:找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
  4. DispatcherServleti调用HandlerAdapter(处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
  6. Controller执行完成返回ModelAndView对象
  7. HandlerAdapter将Controller执行结果ModelAndViewi返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)
  9. ViewReslover解析后返回具体View(视图)
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
  11. DispatcherServlet响应用户

前后端开发,接口开发流程

  1. 用户发送出请求到前端控制器DispatcherServlet

  2. DispatcherServlet收到请求调用HandlerMapping(处理器映射器)

  3. HandlerMapping?找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。

  4. DispatcherServleti调用HandlerAdapter(处理器适配器)

  5. HandlerAdaptera经过适配调用具体的处理器(Handler/Controller)

  6. 方法上添加了@ResponseBody

  7. 通过HttpMessageConverter来返回结果转换为JSON并响应

  8. Springboot的自动配置原理

自动装配,简单来说就是自动把第三方组件的 Bean 装载到 Spring IOC 器里面,不需

要开发人员再去写 Bean 的装配配置。

在 Spring Boot 应用里面,只需要在启动类加上@SpringBootApplication 注解就可以实现自动装配。 @SpringBootApplication 是一个复合注解,它包括了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan,真正实现自动装配的注解是@EnableAutoConfiguration。

自动装配的实现主要依靠三个核心关键技术。

  1. 引入 Starter 启动依赖组件的时候,这个组件里面必须要包含@Configuration 配置类,在这个配置类里面通过@Bean 注解声明需要装配到 IOC 容器的 Bean 对象。

  2. 这个配置类是放在第三方的 jar 包里面,然后通过 SpringBoot 中的约定优于配置思想,把这个配置类的全路径放在 classpath:/META-INF/spring.factories 文件中。这样 SpringBoot 就可以知道第三方 jar 包里面的配置类的位置,这个步骤主要是用到了 Spring 里面的 SpringFactoriesLoader 来完成的。

  3. SpringBoot 拿到第三方 jar 包里面声明的配置类以后,再通过 Spring 提供的ImportSelector 接口,实现对这些配置类的动态加载。在我看来,SpringBoot 是约定优于配置这一理念下的产物,所以在很多的地方,都会看到这类的思想。它的出现,让开发人员更加聚焦在了业务代码的编写上,而不需要去关心和业务无关的配置。

  4. Spring的常见注解

img

  1. SpringMVC的常见注解

img

  1. SpringBoot的常见注解

img

  1. Mybatis的执行流程是什么

  2. 读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件

  3. 构造会话工厂SqlSessionFactory

  4. 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)

  5. 操作数据库的接口,Executor执行器,同时负责查询缓存的维护

  6. Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息

  7. 输入参数映射

  8. 输出结果映射

  9. Mybatis是否支持延迟加载

延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。

Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载

在Mybatisi配置文件中,可以配置是否启用延迟加载lazyLoading Enabled=true|false,默认是关闭的

延迟加载的底层原理知道吗?

使用CGLIB创建目标对象的代理对象

当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,执行sql查询

获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

微服务面试题

  1. Spring Cloud组件有哪些?

因为我项目中使用的是SpringCloudAlibba,所以使用的是阿里巴巴的组件

主要由五个

注册中心/配置中心 Nacos

负载均衡 Ribbon

服务调用 Feign

服务保护 sentinel

服务网关 Gateway

  1. 服务注册和发现是什么意思?Spring Cloud如何实现服务注册发现?

我先说一下eureka的流程吧

服务注册:服务提供者需要把自己的信息注册到eureka,由eureka:来保存这些信息,比如服务名称、ip、端口等等

服务发现:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用

服务监控:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除

nacos还额外增加了一些功能

Nacos与eureka的共同点(注册中心)

都支持服务注册和服务拉取

都支持服务提供者心跳方式做健康检测

Nacos与Eureka的区别(注册中心)

Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

临时实例心跳不正常会被剔除,非临时实例侧不会被剔除

Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

Nacosi还支持了配置中心,eureka则只有注册中心,也是选择使用nacos的一个重要原因

  1. 负载均衡是如何实现的

微服务的负载均衡主要使用了一个组件Ribbon,比如,我们在使用feign远程调用的过程中,底层的负

载均衡就是使用了ribbon

  1. Ribbon负载均衡策略有哪些

  • RoundRobinRule:简单轮询服务列表来选择服务器
  • WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
  • RandomRule:随机选择一个可用的服务器
  • ZoneAvoidanceRule:区域敏感策略,以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮(默认)
  1. 如果想自定义负载均衡策略如何实现?

提供了两种方式:

  1. 创建类实现IRule接口,可以指定负载均衡策略(全局)

  2. 在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略(局部)

  3. 什么是服务雪崩,怎么解决这个问题?

  • 服务雪崩:一个服务失败,导致整条链路的服务都失败的情形
  • 服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑
  • 服务熔断:默认关闭,需要手动打开,如果检测到10秒内请求的失败率超过50%,就触发熔断机制。之后每隔5秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求
  1. 微服务是怎么监控的?

当时项目中采用的skywalking进行监控的

  1. skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,可以针对性的分析和优化。

  2. 如果项目上线,还可以在skywalking设置告警规侧,如果报错,可以分别设置给相关负责人发短信和发邮件,第一时间知道项目的bug情况,第一时间修复

  3. 限流怎么做的?

为什么要限流?

  1. 并发的突然增大(突发流量)
  2. 防止用户恶意刷接口
  • 常规限流:采用漏桶算法进行限流
  • nginx限流

控制速率(突发流量),使用的漏桶算法来实现过滤,让请求以固定的速率处理请求,可以应对突发流量,控制并发数,限制单个ip的链接数和并发链接的总数

  • 网关限流

在spring cloud gateway中支持局部过滤器RequestRateLimiter来做限流,使用的是令牌桶算法

可以根据ip或路径进行限流,可以设置每秒填充平均速率,和令牌桶总容量

  1. 分布式系统理论–CAP和BASE

CAP定理(一致性、可用性、分区容错性)

分布式系统节点通过网络连接,一定会出现分区问题(P)

当分区出现时,系统的一致性(C)和可用性(A)就无法同时满足

BASE理论是针对CAP提出的解决方案

  • 基本可用
  • 软状态
  • 最终一致

解决分布式事务的思想和模型:

  • 最终一致思想:各分支事务分别执行并提交,如果有不一致的情况,再想办法恢复数据(AP)
  • 强一致思想:各分支事务执行完业务不要提交,等待彼此结果。而后统一提交或回滚(CP)
  1. 项目中的微服务使用哪种分布式事务解决方案

可采用的方案(seata|MQ)–我用的就是seata

  1. seata的XA模式,CP,需要互相等待各个分支事务提交,可以保证强一致性,性能差

  2. seata的AT模式,AP,底层使用undo log实现,性能好

  3. seatal的TCC模式,AP,性能较好,不过需要人工编码实现

  4. MQ模式实现分布式事务,在A服务写数据的时候,需要在同一个事务内发送消息到另外一个事务,异步,性能最好

  5. 分布式服务的接口幂等性如何设计

  • 幂等:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致
  • 如果是新增数据,可以使用数据库的唯一索引
  • 如果是新增或修改数据:
  1. 分布式锁,性能较低;
  2. 使用token+redis来实现,性能较好:

第一次请求,生成一个唯一token存入redis,返回给前端

第二次请求,业务处理,携带之前的token,到redisi进行验证,如果存在,可以执行业务,删除token;

如果不存在,则直接返回,不处理业务

  1. 分布式任务调度

以xxl-job为例,此外的还有SpringTask、elastic-job等

  1. 路由策略

xxI-jobJ提供了很多的路由策略,平时用的较多就是:轮询、故障转移、分片广播、

  1. 任务执行失败怎么解决?
  • 路由策略选择故障转移,使用健康的实例来执行任务
  • 设置重试次数
  • 查看日志+邮件告警来通知相关负责人解决
  1. 如果有大数据量的任务同时都需要执行,怎么解决?
  • 让多个实例一块去执行(部署集群),路由策略分片广播
  • 在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行

消息中间件面试题

  1. RabbitMQ-如何保证消息不丢失

  • 开启生产者确认机制,确保生产者的消息能到达队列
  • 开启持久化功能,确保消息未消费前在队列中不会丢失
  • 开启消费者确认机制为auto,由spring确认消息处理成功后完成ack
  • 开启消费者失败重试机制,多次重试失败后将消息投递到异常交换机,交由人工处理
  1. 第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据

  2. 第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化

  3. 第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,如果设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理

  4. RabbitMQ消息的重复消费问题如何解决的

因为我当时处理的支付(订单业务唯一标识),它有一个业务的唯一标识,再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果己经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了

其他解决方案:

其实这个就是典型的幂等的问题,比如,redis分布式锁、数据库的锁都是可以的

  1. RabbitMQ中死信交换机?(RabbitMQ延迟队列了解过吗?)

延迟队列=死信交换机+TTL(生存时间)

如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换

机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实

现了延迟队列的功能了。

我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一

些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超

时时间就行了,相对于死信交换机+TTL要省略了一些步骤

  1. RabbitMQ如果有100万消息堆积在MQ,如何解决(消息堆积怎么解决)

  • 增加更多消费者,提高消费速度
  • 在消费者内开启线程池加快消息处理速度
  • 扩大队列容积,提高堆积上限,采用惰性队列:
  1. 在声明队列的时候可以设置属性x-queue-mode为lazy,即为惰惟队列

  2. 基于磁盘存储,消息上限高

  3. 性能比较稳定,但基于磁盘存储,受限于磁盘O,时效性会降低

  4. RabbitMQ的高可用机制

在生产环境下,可以采用镜像模式搭建的集群,共有3个节点

镜像队列结构是一主多从(从就是镜像),所有操作都是主节点完成,然后同步给镜像节点

主宕机后,镜像节点会替代成新的主(如果在主从同步完成前,主就已经宕机,可能出现数据丢失)

出现丢数据怎么解决呢?

我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Rft协议,强一致。

并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定这个是仲裁队列即可

  1. Kafka是如何保证消息不丢失

需要从三个层面去解决这个问题:

  1. 生产者发送消息到Brocker丢失
  • 设置异步发送,发送失败使用回调进行记录或重发
  • 失败重试,参数配置,可以设置重次数
  1. 消息在Brocker中存储丢失
  • 发送确认acks,选择all,让所有的副本都参与保存数据后确认
  1. 消费者从Brocker接收消息丢失
  • 关闭自动提交偏移量,开启手动提交偏移量
  • 提交方式,最好是同步+异步提交

回答:

第一个是生产者发送消息的时候,可以使用异步回调发送,如果消息发送失败,我们可以通过回调获

取失败后的消息信息,可以考虑重试或记录日志,后边再做补偿都是可以的。同时在生产者这边还可

以设置消息重试,有的时候是由于网络抖动的原因导致发送不成功,就可以使用重试机制来解决

第二个在broker中消息有可能会丢失,我们可以通过kafka的复制机制来确保消息不丢失,在生产者发

送消息的时候,可以设置一个acks,就是确认机制。我们可以设置参数为al,这样的话,当生产者发送消息到了分区之后,不仅仅只在leader分区保存确认,在follwer分区也会保存确认,只有当所有的副本都保存确认以后才算是成功发送了消息,所以,这样设置就很大程度了保证了消息不会在broker丢失

第三个有可能是在消费者端丢失消息,kafka消费消息都是按照offset进行标记消费的,消费者默认是自

动按期提交己经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或

丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消

费的位置,这样就可以避免消息丢失和重复消费了

  1. Kafka中消息的重复消费问题如何解决的?

  • 关闭自动提交偏移量,开启手动提交偏移量
  • 提交方式,最好是同步+异步提交
  • 幂等方案

回答:

kafka消费消息都是按照offseti进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了

  1. Kafka是如何保证消费的顺序性

问题原因:

一个topic的数据可能存储在不同的分区中,每个分区都有一个按照顺序的存储的偏移量,如果消费者关联了多个分区不能保证顺序性

解决方案:

  • 发送消息时指定分区号
  • 发送消息时按照相同的业务设置相同的key

回答:

kafka默认存储和消费消息,是不能保证顺序性的,因为一个topic数据可能存储在不同的分区中,每个

分区都有一个按照顺序的存储的偏移量,如果消费者关联了多个分区不能保证顺序性

如果有这样的需求的话,我们是可以解决的,把消息都存储同一个分区下就行了,有两种方式都可以

进行设置,第一个是发送消息时指定分区号,第二个是发送消息时按照相同的业务设置相同的ky,因

为默认情况下分区也是通过key的hashcode值来选择分区的,hash值如果一样的话,分区肯定也是一样

  1. Kafka的高可用机制

  2. 集群:

一个kafka集群由多个oroker3实例组成,即使某一台宕机,也不耽误其他broker继续对外提供服务

  1. 复制机制:

一个topic有多个分区,每个分区有多个副本,有一个leader,其余的是follower,副本存储在不同的broker中所有的分区副本的内容是都是相同的,如果leader发生故障时,会自动将其中一个follower提升为leader,保证了系统的容错性、高可用性

  1. 解释一下复制机制中的ISR

ISR(in-sync replica)需要同步复制保存的follower

分区副本分为了两类,一个是ISR,与ileader副本同步保存数据,另外一个普通的副本,是异步同步数据,当leader挂掉之后,会优先从ISR副本列表中选取一个作为leader

  1. Kafka数据清理机制

Kafka存储结构

  • Kafka中topic的数据存储在分区上,分区如果文件过大会分段存储segment
  • 每个分段都在磁盘上以索引(xxxx.index)和日志文件(xxx.log)的形式存储
  • 分段的好处是,第一能够减少单个文件内容的大小,查找数据方便,第二方便kafka进行日志清理。

日志的清理策略有两个:

  • 根据消息的保留时间,当消息保存的时间超过了指定的时间,就会触发清理,默认是168小时(7天)
  • 根据topic存储的数据大小,当topic所占的日志文件大小大于一定的阈值,则开始删除最久的消息。(默认关闭)
  1. Kafka中实现高性能的设计

  • 消息分区:不受单台服务器的限制,可以不受限的处理更多的数据
  • 顺序读写:磁盘顺序读写,提升读写效率
  • 页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问
  • 零拷贝:减少上下文切换及数据拷贝
  • 消息压缩:减少磁盘1O和网络1O
  • 分批发送:将消息打包批量发送,减少网络开销

集合面试题

  1. 为什么数组索引从0开始呢?假如从1开始不行吗?

在根据数组索引获取元素的时候,会用索引和寻址公式来计算内存所对应的元素

数据,寻址公式是:数组的首地址+索引乘以存储数据的类型大小

如果数组的索引从1开始,寻址公式中,就需要增加一次减法操作,对于CPU来说

就多了一次指令,性能不高。

img

  1. ArrayList底层的实现原理是什么

  2. ArrayList底层是用动态的数组实现的

  3. ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10

  4. ArrayListi在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组

  5. ArrayList在添加数据的时候

  • 确保数组已使用长度(size)加1之后足够存下下一个数据
  • 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍)
  • 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
  • 返回添加成功布尔值。
  1. ArrayList list =new ArrayList(10)中的list扩容几次

该语句只是声明和实例了一个ArrayList,指定了容量为10,未扩容

  1. 在使用ArrayList和LinkedList时,如何保证线程安全

如果需要保证线程安全,有两种方案:

  • 在方法内使用,局部变量则是线程安全的
  • 使用线程安全的ArrayListi和LinkedList
List<Object>syncArrayList Collections.synchronizedList(new ArrayList<>());
List<Object>syncLinkedList Collections.synchronizedList(new LinkedList<>());
  1. hashMapl的寻址算法

计算对象的hashCode

再进行调用hash()方法进行二次哈希,hashcode值右移16位再异或运算,让哈希分布更为均匀最后(capacity-1)&hash得到索引

  1. 为何HashMap的数组长度一定是2的次幂?

  2. 计算索引时效率更高:如果是2的次幂可以使用位与运算代替取模

  3. 扩容时重新计算索引效率更高:hash&oldCap==0的元素留在原来位置,否则新位置=旧位置+oldCap

  4. jdk1.7的hashmap扩容时发生的死循环问题

在jdk1.7的nashmap中在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能导致死循环

比如说,现在有两个线程

线程一:读取到当前的hashmap数据,数据中一个链表,在准备扩容时,线程二介入

线程二:也读取nashmap,直接进行扩容。因为是头插法,链表的顺序会进行颠倒过来。比如原来的顺序是AB,扩容后的顺序是BA,线程二执行结束。

线程一:继续执行的时候就会出现死循环的问题。

线程一先将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,B的neXt指向了A,所以B->A->B,形成循环。

当然,JDK8将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),尾插法,就避免了jdk7中死循环的问题。

并发编程面试题:

  1. 进程和线程的区别

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)
  1. 并发和并行的区别

现在都是多核CPU,在多核CPU下

  • 并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU
  • 并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程
  1. 创建线程的方式有哪些?

  • 继承Thread类
  • 实现runnable接口
  • 实现Callable接口
  • 线程池创建线程(项目中使用方式)
  1. 使用runnable和callable都可以创建线程,它们有什么区别呢?

  • Runnable接口run方法没有返回值
  • Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  • Callable接口的call(方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
  1. 线程的run()和start()有什么区别?

  • start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
  • run():封装了要被线程执行的代码,可以被调用多次。
  1. 线程包括哪些状态,状态之间是如何变化的

  2. 线程包括哪些状态

新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、时间等待(TIMED_WALTING)、终止(TERMINATED)

  1. 线程状态之间是如何变化的

创建线程对象是新建状态

调用了start()方法转变为可执行状态

线程获取到了CPU的执行权,执行结束是终止状态

在可执行状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态:

如果没有获取锁(synchronized或lock)进入阻塞状态,获得锁再切换为可执行状态

如果线程调用了wait(0方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态

如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态

  1. 新建T1、T2、T3三个线程,如何保证它们按顺序执行?

使用join()方法

  1. wait和sleep()方法的不同

共同点

wait0,wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态

不同点

  1. 方法归属不同

sleep(long)是Thread的静态方法

而wait0,wait(long)都是Object的成员方法,每个对象都有

  1. 醒来时机不同

执行sleep((long)和wait(ong)的线程都会在等待相应毫秒后醒来

wait(long)和wait0还可以被notify唤醒,wait0如果不唤醒就一直等下去

它们都可以被打断唤醒

  1. 锁特性不同(重点)

wait方法的调用必须先获取wait对象的锁,而sleep则无此限制

wait方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)

而sleep如果在synchronized代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)

  1. 如何停止一个正在运行的线程?

有三种方式可以停止线程

  • 使用退出标志,使线程正常退出,也就是当u方法完成后线程终止
  • 使用stop方法强行终止(不推荐,方法已作废)
  • 使用interrupt方法中断线程
  1. 打断阻塞的线程(sleep,wait,join)的线程,线程会抛出InterruptedException异常

  2. 打断正常的线程,可以根据打断状态来标记是否退出线程

  3. synchronized关键字的底层原理

  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
  • 它的底层由monitor实现的,monitor是jvm级别的对象(c++实现),线程获得锁需要使用对象(锁)关联monitor
  • 在monitor内部有三个属性,分别是owner、.entrylist、waitset
  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程

但是Monitor实现的锁属于重量级锁,于是jdk1.6对synchronized做了优化,引入了锁升级

Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。

  • 重量级锁:底层使用的Monito实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
  • 轻量级锁:线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性
  • 偏向锁:一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令
  1. 谈谈JMM(Java内存模型)

  • JMM(Java Memory Model)Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性
  • JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
  • 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存
  1. 说一下CAS

  • CAS的全称是:Compare And Swap(比较再交换);它体现的一种乐观锁的思想在无锁状态下保证线程操作数据的原子性。
  • CAS使用到的地方很多:AQS框架、AtomicXXX类
  • 在操作共享变量的时候使用的自旋锁,效率上更高一些
  • CAS的底层是调用的Unsafe类中的方法,都是操作系统提供的,其他语言实现
  1. 对volatile的理解

  2. 保证线程间的可见性

用volatile修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见

  1. 禁止进行指令重排序

指令重排:用volatile修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果

  1. 什么是AQS?

  • 是多线程中的队列同步器。是一种锁机制,它是做为一个基础框架使用的,像ReentrantLock、.Semaphore都是基于AQS实现的
  • AQS内部维护了一个先进先出的双向队列(CLH队列),队列中存储的排队的线程
  • 在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中的有一个线程修改成功了state为1,则当前线程就相等于获取了资源
  • 在对state修改的时候使用的cas操作,保证多个线程修改的情况下原子性
  1. ReentrantLock的实现原理

ReentrantLock表示支持重新进入的锁,调用Iock方法获取了锁之后,再

次调用ock,是不会再阻塞

ReentrantLock主要利用CAS+AQS队列来实现

支持公平锁和非公平锁,在提供的构造器的中无参默认是非公平锁,也可

以传参 设置为公平锁

  1. synchronized和Lock有什么区别?

  • 语法层面

synchronized是关键字,源码在jvm中,用c++语言实现

Lock是接口,源码由jdk提供,用java语言实现

使用synchronized时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁

  • 功能层面

二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能

Lock提供了许多synchronized不具备的功能,例如公平锁、可打断、可超时、多条件变量

Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock(读写锁)

  • 性能层面

在没有竞争时,synchronized做了很多优化,如偏向锁、轻量级锁,性能不赖

在竞争激烈时,Lock的实现通常会提供更好的性能

  1. 聊一下ConcurrentHashMap

  2. 底层数据结构:

JDK1.7底层采用分段的数组+链表实现

JDK1.8采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树

  1. 加锁的方式

JDK1.7采用Segment分段锁,底层使用的是ReentrantLock

JDK1.8采用CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好

  1. 导致并发程序出现问题的根本原因是什么

  2. 原子性 synchronized、lock

  3. 内存可见性 volatile、synchronized、lock

  4. 有序性 volatile

  5. 线程池的核心参数以及执行原理

  • corePoolSize核心线程数目
  • maximumPoolSize最大线程数目=(核心线程+敕急线程的最大数目)
  • keepAliveTime生存时间-救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
  • unit时间单位-救急线程的生存时间单位,如秒、毫秒等
  • workQueue-当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
  • threadFactory线程工厂-可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  • handler拒绝策略-当所有线程都在繁忙,workQueue也放满时,会触发拒绝策略

img

拒绝策略

  1. AbortPolicy:直接抛出异常,默认策略;

  2. CallerRunsPolicy:用调用者所在的线程来执行任务;

  3. DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

  4. DiscardPolicy:直接丢弃任务;

  5. 线程池中有哪些常见的阻塞队列

workQueue-当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务

  1. ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。

  2. LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。

  3. DelayedWorkQueue:是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的

  4. SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

  5. 如何确定核心线程数

  6. 高并发、任务执行时间短→(CPU核数+1),减少线程上下文的切换

  7. 并发不高、任务执行时间长

  • IO密集型的任务→(CPU核数2+1)
  • 计算密集型任务→(CPU核数+1)
  1. 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)

  2. 线程池的种类有哪些

  3. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

  4. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FO)执行

  5. newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

  6. newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行

  7. 为什么不建议用Executorst创建线程池

  8. FixedThreadPool SingleThreadPool

允许的请求队列长度为Integer.MAX VALUE,可能会堆积大量的请求,从而导致OOM。

  1. CachedThreadPool

允许的创建线程数量为Integer.MAX VALUE,可能会创建大量的线程,从而导致OOM。

  1. 如何控制某个方法允许并发访问线程的数量

在多线程中提供了一个工具类Semaphore,信号量。在并发的情况下,可以控制方法的访问量

  1. 创建Semaphorex对象,可以给一个容量

  2. acquire()可以请求一个信号量,这时候的信号量个数-1

  3. release0释放一个信号量,此时信号量个数+1

  4. 谈谈你对ThreadLocal的理解

  5. ThreadLocal可以实现【资源对象】的线程隔离,让每个线程各用各的【资源对象】,避免争用引发的线程安全问题

  6. ThreadLocal同时实现了线程内的资源共享

  7. 每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象

  • 调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
  • 调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
  • 调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
  1. ThreadLocall内存泄漏问题

ThreadLocalMap中的key是弱引用,值为强引用;key会被GC释放内存,关联value的内存并不会释放。建议主动remove释放key,value

JVM面试题

  1. 说一下类装载的执行过程?

  • 加载:查找和导入class文件
  • 验证:保证加载类的准确性
  • 准备:为类变量分配内存并设置类变量初始值
  • 解析:把类中的符号引用转换为直接引用
  • 初始化:对类的静态变量,静态代码块执行初始化操作
  • 使用:JVM开始从入口方法开始执行用户的程序代码
  • 卸载:当用户程序代码执行完毕后,VM便开始销毁创建的Class对象。
  1. java内存泄露的排查思路?

内存泄漏通常是指堆内存,通常是指一些大对象不被回收的情况

1、通过jmap或设置jvm参数获取堆内存快照dump

2、通过工具,VisualVM去分析dump文件,VisualVM可以加载离线的dump文件

3、通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题

4、找到对应的代码,通过阅读上下文的情况,进行修复即可

  1. CPU飙高排查方案与思路?

1.使用top命令查看占用cpu的情况

2.通过top命令查看后,可以查看是哪一个进程占用cpu较高

3.使用ps命令查看进程中的线程信息

4.使用jstacki命令查看进程中哪些线程出现了问题,最终定位问题

设计模式面试题

  1. 工厂模式
  • 简单工厂

所有的产品都共有一个工厂,如果新增产品,则需要修改代码,违反开闭原则是一种编程习惯,可以借鉴这种编程思路

  • 工厂方法模式

给每个产品都提供了一个工厂,让工厂专门负责对应的产品的生产,遵循开闭原侧项目中用的最多

  • 抽象工厂方法模式

如果有多个纬度的产品需要配合生产时,优先建议采用抽象工厂(工厂的工厂)一般的企业开发中的较少