【java常见面试题】

该博客围绕Java、Spring Boot和微服务展开。介绍了事务特性、隔离级别,慢查询优化和缓存命中率提升方法;阐述了MySQL索引、锁、事务实现等知识;还涉及Java多线程、JVM、Spring框架原理,以及微服务组件、分布式事务、消息队列等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

吐血整理B站java面试题:包含java基础、数据库、spring、springboot、spring cloud相关面试题,持续更新

事务的基本特性和隔离级别

事务的基本特性ACID
原子性:一个事务中的操作要么全部成功,要么全部失败
一致性:
隔离性:一个事务在提交之前,对于其他事务是不可见的
持久性:事务一旦提交,所做的修改会永久保存到数据库中
事务的隔离级别和存在的问题:
读已提交:不可重复读,两次读取的结果不一致
读未提交:脏读,读到其他事务修改了但是没有提交的数据
可重复读:每次读取的数据相同,可能产生幻读,即与数据库数据修改后数据不一致
串行:不能并发,会给每一行上锁

如何进行慢查询优化:

MySQL可以开启慢查询日志,日志中会记录到慢查询对应的sql语句和执行时间等信息,对于经常访问的数据需要进行优化
优化思路:
1、使用explain 进行分析,添加合适的索引
2、检查所使用的字段是否必须,未使用到的字段不需要查询出来
3、检查group by 等函数可能会导致索引失效
4、关联多个表时,可能会导致查询缓慢,建议不超过三张表关联查询
5、检查所使用的索引是否为最优索引,可以指定使用哪个索引
6、检查表中数据是否过多,过多可以考虑分库分表
7、检查服务器性能配置,优化服务器性能

怎么提高缓存的命中率?例如Redis缓存

1、将缓存空间扩大,能够存储的缓存数据更多
2、提高缓存的更新频率
3、提前将缓存数据加载
4、调整缓存的存储类型,可以使用多个字段进行缓存
什么是索引下推?
在执行辅助索引的时候,先执行where语句中的内容,筛选数据,减少回表的次数,
需要数据库进行配置

Buffer pool :缓冲池

不直接查询数据库,减少io操作,先在缓冲池中查询符合条件数据
缓冲池由缓存数据页和缓存控制块两个组成,控制器保存了缓存所属的表空间和数据页编号等信息,page页默认占16k,控制块占0.8k
Mysql中有一个hash表,通过表空间+数据页号这个key,可以拿到对应的控制块,就能判断一个页是否在缓冲区

Innodb如何管理page页?

Page页有三个类型,都是用的链表进行存储
Free page 未使用的page
Clean page 使用了但是没有修改的page
Drity page 已经被修改了的页,需要在空闲的时候持久化,刷到磁盘中

Innodb是如何实现事务的?

Innodb是通过buffer pool 、logBuffer、redolog、 undolog等组件实现事务的,先在缓冲池中更新,事务提交后再将缓冲池中的数据刷新到数据库中,以update语句为例:
1、innodb在收到一个update语句后,会先找到数据所在的page页并将该页缓存到buffer pool中
2、执行update语句,在内存中修改buffer pool的数据
3、针对该update语句生成一个redolog对象,并存入到logbuffer中,也是在内存
4、对该语句生成一个undolog对象,用于事务回滚
5、如果事务提交,则将redolog对象进行持久化,后续有其他机制将buffer pool中的数据刷到磁盘中持久化
6、如果事务回滚,则利用undolog对象进行回滚

mysql缓冲区类型:

Free list 空闲缓冲区 管理free page
Flush list 表示需要刷新到磁盘的缓冲区 管理drity page
Lru list 表示正在使用的缓冲区,管理 clean page和 drity page

写缓冲区changeBuffer只适用于非唯一索引普通页?

changeBuffer是缓冲区Buffer pool中的一部分 数据修改后先在changeBuffer更新到Buffer pool ,再由Buffer pool 缓存到磁盘中。
因为唯一索引会需要判断数据唯一性,直接去数据库中查询,不会在缓冲区查询

Mysql的lru算法为什么要改进(最近最少使用)

尾部淘汰制:新数据添加到链表头部,被使用到的数据页添加到链表头部
缺点: 如果全表扫描,则会把热点数据刷到链表后面被淘汰
改进后将数据分为冷数据区和热数据区 热数据区占63%,第一次查询的数据替换到冷数据区头部midpoint处,如果存活时间超过1s时放到热数据区头部

索引的原理:

索引就是将无序的数据进行有序的排列存储,使用B+树(二叉平衡树)的数据结构存储索引,即倒排表
1、把创建了索引的列的内容按照btree算法,进行排序
2、对排序结果生成一个倒排表,并维护到数据库中
3、在倒排表内容上拼接数据真实的地址链
4、查询的时候先通过索引拿到倒排表上的数据,再通过地址链拿到具体的数据

使用索引一定会提升效率吗?

使用索引不一定会提升效率
1、索引会占用物理空间,
2、对表数据的修改,索引也需要动态维护
3、索引字段过多后会影响查询速率(5个字段)

索引创建原则:

索引的原理:将无序的字段进行有序的排列,就不用查询全表
1、在经常使用到的字段上创建索引
2、在主键和外键上创建索引
3、在排序字段上创建索引
4、在经常使用到的where条件字段上创建索引
5、对于频繁修改的列不要创建索引,修改数据索引也要更新
6、重复数据太多,字段区分度不高的字段不适合创建索引
7、尽量扩展索引而非新增索引

索引覆盖是什么?

索引覆盖是指我们查询的数据字段在索引中都存在了,可以直接返回数据,而不需要再去回表通过主键id查询

MySQL数据库page页分为三部分:

通用部分,数据记录部分和数据目录部分。

Mysqld的聚簇索引和非聚簇索引:

聚簇索引的主键索引数据和表数据是同一份数据,辅助索引中只存储主键值,再通过主键值去主键索引数据中查询
非聚簇索引则在索引记录中不保存表数据,只保存主键值,需要再通过主键和地址链去数据表中进行查询
聚簇索引比非聚簇索引查询效率高,但是更新数据时效率比较低

Mysql中索引使用B+树的特点

B+树是对B树的升级,B+树的非叶子节点存储的是索引数据,叶子节点是存储的表的所有数据 ,B+树叶子节点之间有指针指向子节点
非叶子节点的数据都会在叶子节点上冗余,也就是叶子结点存储了所有元素,且排好序了
Mysql一页可以存储16k数据,两层B+树可以存储2000万行数据,超过这个值会增加层数,导致查询效率变低,可以考虑分库分表。

Select,poll、epoll三种事件模型的区别

Select模型:使用数组存储socket连接文件描述符,容量固定,需要轮询判断是否发生io事件,遍历数组里面的socket连接,查看是否需要读取文件。
Poll模型:使用链表保存socket连接文件描述符,容量不固定,也需要轮询判断是否发生io事件
Epoll模型:是一种事件通知模型,当发生了io事件时,程序才进行io操作,不需要主动轮询,效率比较高

Explain语句结果字段的含义:

在这里插入图片描述

如何连接mysql数据库服务?

1、引入数据库连接的依赖
2、去除默认的h2数据库依赖
3、在application.properties文件中进行数据库连接配置。

Mysql中锁有哪些?

按锁的粒度进行区分:
1、表锁:整个表都被锁住,其他线程不能读写该表,并发低
2、行锁:只锁某一行记录,粒度细,并发高
3、间隙锁:粒度适中,锁的是两行记录中间的记录区间
还可以分为共享锁(读锁)和排他锁(写锁)
一个事务给某行加了读锁,其他事务可以读,但是不能写
一个事务给某行加了写锁,其他事务不能进行读写操作
还可以分为悲观锁和乐观锁,上面的行锁和表锁都是悲观锁

Mysiam和innodb的区别

Mysiam:
1、不支持事务
2、只支持表级锁
3、会存储数据总行数
4、主键索引采用非聚集索引,索引文件的数据域存储指向数据文件的指针,辅索引和主索引基本一致,只是辅助索引不需要保证唯一性
Innodb:
支持ACID事务
支持行级锁及外键约束,可以写并发
不存储数据总行数
主键索引采用聚集索引(索引的数据域存储数据文件本身),辅助索引存储主键值
在读写分离的系统中,从数据库只需要读操作,可以使用mysiam,同步数据比较快。

Java死锁如何避免?

Java死锁需要满足四个条件
1、一个资源只能被一个线程使用
2、一个线程在阻塞等待资源时,不释放已占用的资源
3、一个线程已经获得的资源,不能被其他线程强制剥夺
4、若干线程形成了首尾相连的循环等待资源状态
前三个是线程基本特性,没法改变,只有第四个需要注意
1、注意加锁的顺序,两个线程都先加锁a再加锁b
2、注意加锁的时限,设置超时时间
3、注意死锁检查,是一种预防机制,使用jstack命令可以查看java线程是否死锁

Java中实现多线程的方式?

一共四种
1、继承Thread类,重写run方法
2、实现Runable接口,实现run方法
3、实现callable接口,实现call方法,会返回一个FutureTask ,可以拿到线程的返回值
4、使用线程池
其实本质上都是实现runable接口

Java中线程的状态:

一共有六种状态
new 一个线程得到新建状态(NEW)的线程,
执行start方法得到一个就绪状态(RUNNABLE),
当调用wait方法时,线程变为等待状态(WATING)需要手动唤醒,
调用sleep方法得到时间等待状态(TIMED_WATING)时间到了后变为就绪,
当线程竞争锁失败是阻塞状态(BLOCKED)
线程执行结束变成结束状态(TERMINATED)

线程池执行任务为什么要添加一个非核心空任务的线程?

线程池的核心线程个数允许设置为0,当任务进来时如果不添加线程,就没有线程可以执行任务,线程池还可以设置核心线程超时关闭属性,设置为true时,核心线程会被干掉,可能会导致没有线程可用,需要添加非核心空任务的线程处理。

线程结束的方法:

1、调用stop方法,线程会立即停止
2、Interrupt方式,会有一个中断标记位thread.Interrupt会将线程的中断标记位的值改为true,默认是false
3、共享变量:需要在线程执行方法里面留下判断终止的条件判断

Java线程的wait和sleep方法区别

1、sleep方法属于Thread类里面的方法,wait是Object的方法
2、Sleep不会中断锁,不需要手动唤醒,wait会中断持有的锁,需要调用notify方法唤醒
3、Sleep可以在不持有锁的时候执行,wait需要持有锁才能执行

并发编程的三大特性:

原子性:线程运行过程中,其他线程不会对当前线程造成影响
保证原子性:使用synchronized关键字、使用CAS,使用lock锁,使用ThreadLocal保证操作的是当前线程的资源

可见性:在一个线程对共享资源的操作对于其他线程来说是可见的
保证可见性:使用volatile修饰,修改后会同步到内存,使用synchronized关键字、使用lock锁,使用final修饰

有序性:多线程执行不影响最后的结果,防止指令重排

线程池工作流程:

添加任务到线程池–》判断任务是否为null–》判断是否还有核心线程未创建,有未创建的核心线程则会先创建核心线程,没有核心线程未创建的话–》判断线程池是否异常,将任务添加到阻塞队列中–》如果线程池状态不正常或添加阻塞队列失败,则判断是否添加非核心线程处理,如果添加不了,就执行淘汰策略

线程池的五个状态状态?

New一个线程池后,线程为running状态,可以处理任务,
调用shutdown()方法后,为shutdown状态,线程不会接受新任务,运行中的和阻塞队列中的任务会执行结束
调用shutdownNow()方法后,线程是stop状态、不会接受新任务,且运行中的任务也会被中断
调用上面两个方法后,线程队列中没有任务就变成tidying这个过渡状态
tidying状态执行terminated()方法线程编程terminated状态,销毁状态。

Jdk自带的线程池构建方式一共五种:

1、newfixedThreadPool 固定线程的线程池,创建的线程都是核心线程,但是任务队列无界
2、NewSingleThreadPool 单例线程池,只要一个线程执行任务,任务执行有顺序,但是任务队列无界,任务太多可能会导致任务积压,内存溢出
3、NewCachedThreadPool 缓存线程池,没有核心线程,每次执行任务都要新建线程,执行效率低,当任务太多的时候会创建很多线程,会内存不足
4、NewScheduleThreadPool 定时任务线程池,可以延迟或定期执行任务
5、NewWorkStealingPool 其他线程都只有一个阻塞队列,他的每一个线程都有一个队列,当前线程队列任务执行完后会去其他线程队列中获取任务执行
都会有一些不足,所以阿里巴巴建议自定义创建线程池,可以配置不同的任务队列类型

如果提交任务时任务队列满了会发生什么?

如果是无界队列,那么可以无限提交任务
如果是有界队列,会先判断线程池是否有核心线程未被创建,如果有的话新增一条核心线程处理当前任务,如果没有的话会使用拒绝策略进行拒绝。
ReentrantLock和synchronized 区别
ReentrantLock是java类,synchronized是一个关键字
Synchronized作用于对象和方法,类上,ReentrantLock是作用于代码块
Synchronized不需要手动释放锁,ReentrantLock需要手动lock()加锁,unlock()解锁
Synchronized只支持非公平锁,reentrantLock支持公平锁和非公平锁,调用构造函数传参
Synchronized引入了锁升级后两者效率差不多,Synchronized使用比较便捷

CAS是什么?

比较和替换,当要修改某个值时,先拿修改前的值和内存中的值进行比较,如果和预期值相同,说明没有其他线程对数据修改,则对数据进行修改,如果和预期值不同,则不进行修改,说明已经有其他线程修改了,可能有ABA问题,可以增加版本号解决。Cas自旋过多可能会占用CPU资源,可以指定自旋次数,超过后线程挂起

AQS是什么?

AQS是一个抽象类AbstractQueuedSynchronizer,ReentrantLock和线程池、阻塞队列,seamphor中有用到,里面维护了一个volatile修饰的int类型的state,用于判断资源是否被线程持有,大于0说明被持有,还有一个双向链表,用于存储等待锁的线程,线程作为节点Node存入。

AQS是怎么实现重入锁的?

内部维护了一个state属性记录锁重入次数,维护了一个双向链表队列存储等待锁的线程,
在加锁的时候,会判断要加锁的线程和当前持有锁的线程是否同一个,是的话state加1,否的话加锁失败,在链表队列等待

CAS的缺点:

1、ABA问题:添加版本号
2、自旋时间过长会影响效率:限定最大自旋数,超过挂起线程
3、范围不能灵活控制:多个类型的变量组成一个新的变量

Synchronized实现原理?

Synchronized修饰对象,新建对象时在内存中会有保存对象头等信息,对象头有个属性markword,在无锁或偏向锁时后三位表示锁状态,轻量级锁和重量级锁在最后两位保存锁的状态信息。

Synchronized在jdk1.6的优化:

锁消除:加了锁和不加锁对代码没有影响,如非多线程代码块,jit会

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值