Java高频面试题
2023-04-26 18:11:47 16 举报
登录查看完整内容
Java高频面试题
作者其他创作
大纲/内容
是一个集合类的顶级接口
其直接继承接口有 List 与 Set
为各种具体的集合提供了最大化的统一操作方式
Collection
集合类的一个工具类/帮助类
提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
Collections
Collection 和 Collections 区别
两者都不是线程安全
Arraylist——Object 数组
LinkedList——双向链表
底层数据结构不同
ArrayList效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
随机访问、修改时
LinkedList效率更高,因为ArrayList是数组,在进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
插入、删除时
总结:数组修查快增删慢,链表增删快修查慢
效率不同
jdk8的ArrayList,在第一次调用 add 方法就会创建容量为 10 的数组,无特殊情况扩容一般是 1.5 倍
Linkedlist 不会扩容,链表一直加
扩容不同
ArrayList主要控件开销在于需要在List列表预留一定空间
LinkList主要控件开销在于需要存储节点信息以及节点指针
主要控件开销不同
ArrayList 和 LinkList区别
HashSet 是 Set 接口的主要实现类,底层是 HashMap,线程不安全,可存储 null
LinkedHashSet 是 HashSet 的子类,能够按照添加的顺序遍历;
TreeSet 底层使用红黑树,元素是有序的,排序的方式有自然排序和定制排序。
HashSet、LinkedHashSet 和 TreeSet
HashMap:无序、非线程安全、效率较高、允许null值(key和value都允许)
HashTable:无序、线程安全、效率较低、不允许key值为null
TreeMap:有序(基于红黑树对所有的key进行排序)、非线程安全、没有调优选项(因为该树总处于平衡状态)、不允许key值为null
HashMap、HashTable、TreeMap区别
HashMap:key可为nullConCurrentHashMap:key不可为空
没有锁机制,不是线程安全
HashMap
对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用 lock 锁进行保护。
相对于 HashTable 的 synchronized 锁的粒度更精细了一些,并发性能更好。
ConcurrentHashMap
线程安全
HashMap 和 ConcurrentHashMap 的区别
线程不安全
允许键为空(一个)
元素是无序的,且顺序会不定时改变
基本特性
JDK1.7,由“数组+链表”组成,数组是主体,链表则是为了解决哈希冲突而存在的
JDK1.8,由“数组+链表+红黑树”组成。当链表过长,会影响性能,链表搜索复杂度是O(n),红黑树是O(logn)。
底层数据结构
当链表长度>=8,且数组长度>=64,才会转红黑树
当链表长度<=6时,才会转化为链表(去树化),中间有个差值7,防止频繁转换
为什么阈值为8,根据统计学的泊松分布
链表和红黑树的转换
默认值:加载因子0.75,初始长度为16,阈值为12(16*0.75)
当存新值时(不是替换),map的大小如果大于阈值,触发扩容
扩容条件
注意,是先存放后扩容
扩容位原来的2倍
加载因子/扩容
根据key计算hash值,找到该元素在数组中存储的下标
如果数组为空,调用resize初始化
如果没有哈希冲突,直接放在对应的数组下标里
如果冲突,则判断key是否存在,存在则覆盖掉value
如果key不存在,且是红黑树,则插入键值
数组长度<64,进行扩容
数组长度>=64,转红黑树
如果key不存在,且是链表,则插入键值,再判断如果链表长度>=8(此处长度不包含新节点)
最后判断是否大于阈值,是则扩容
PUT流程
remove()时,左右节点为空,去树化,转为链表
在resize()时也会发生去树化
在put时,如果key存在,会返回旧值,否则返回null
当key为null时,存在数组下标为0里面
7 只使用链表,8 使用链表+红黑树
7是先扩容再存放,8是先存放后扩容
7 使用头插法插入元素,在多线程的环境下有可能导致环形链表的出现,扩容的时候会导致死循环。因此,8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了,但JDK1.8 的 HashMap 仍然是线程不安全的
1.7和1.8插入时的区别
int h = key.hashCode();h ^ (h >>> 16)
hashCode 异或 无符号右移16位的hashCode
public native int hashCode();
key 的存储索引是怎么计算?
HashMap 构造函数允许用户传入的容量不是2的n次方,因为它可以自动地将传入的容量转换为2的n 次方。会取大于或等于这个数的 且最近的2次幂作为 table 数组的初始容量,使用tableSizeFor(int)方法,如 tableSizeFor(10) = 16(2 的 4 次幂),tableSizeFor(20) = 32(2 的 5 次幂),也就是说 table 数组的长度总是 2 的次幂
初始化大小
modCount
https://www.pudn.com/news/62a731ef3934cd25af8301a3.html
推荐网站
其他
集合
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待并尝试获取,不断的判断锁是否能够被成功获取,直到成功获取到锁才会退出循环。
自旋锁
分段锁其实是一种锁的设计,并不是具体的一种锁
分段锁设计的目的是细化锁的粒度,当操作不需要更新整个对象时,就仅仅针对对象中的部分进行加锁操作。
如ConcurrentHashMap的实现
分段锁
并不是具体的两种锁的实现,而一种设计思想,一种看待并发同步的角度
说明
对于同一数据的并发操作,悲观锁认为这是一定会修改数据的,因此会采取加锁的方式实现同步。
由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待
也就是说,持有数据就会上锁
select * from user where name=\"sun\" for update
这条sql语句从user 表中选取name=sun的记录并对其加锁,
那么其他写操作再这个事务提交之前,都不会对这条数据进行操作,起到了独占和排他的作用。
案例
Java中的Synchronized就是悲观锁
Synchronized
ReentrantLock底层由AQS实现:先尝试CAS乐观锁去获取锁,获取不到,才会转换为悲观锁
ReentrantLock等独占锁(互斥锁)
实现
保证多线程下顺序读写,防止脏数据产生
优点
浪费CPU、执行效率低
占用资源,使其他线程无法工作
缺点
优/缺点
适用读少写多,该场景下,冲突多,所以悲观锁很适合
比如MySQL的for update
传统关系型数据库里,有很多这种锁机制,比如行锁,表锁等,都是在做操作之前先上锁
适用场景
悲观锁
认为读多写少,遇到并发的可能性低,所以不会上锁
读时不加锁,更新时通过业务实现锁
CAS(Compare-and-Swap, 即比较并替换) 算法
一种有名的无锁算法
不使用锁的情况下,实现多线程之间的变量同步
Java中的乐观锁基本都是通过CAS操作实现的
一些以Atomic为开头的一些原子类都使用CAS作为其实现方式
使用这些类在多核CPU的机器上会有比较好的性能。
Java从JDK 1.5开始支持
什么是CAS算法?
保证原子性,需加锁,如synchronzied或ReentrantLock
如何保证原子性?
V:需要读写的内存值
A:进行比较的值
B:拟写入的新值
只有A和V内存值相同,才将V修改为B,否则什么都不做
涉及三要素
在没有线程被阻塞的情况下,实现变量的同步
非阻塞的轻量级的乐观锁,在资源竞争不激烈的情况下性能高,相比synchronized重量锁,synchronized会进行比较复杂的加锁、解锁和唤醒操作。
一开始值为A,但被其他线程修改过,但值被修改成A
ABA问题
自旋时间过长,消耗CPU资源,如果资源竞争激烈,多线程自旋长时间消耗资源
在高并发的情况下,性能可能还不如悲观锁
CAS算法
也叫DCAS,是CAS的变种,加了个版本号标识
在数据表中加上一个version字段来实现,表示数据被修改的次数, 当执行写操作并且写入成功后, version=version+1,当线程A要更新数据时,在读取数据的同时也会读取version值,在提交更新时, 若刚才读取到的version值为当前数据库中的version值相等时,才更新, 否则重试更新操作,直到更新成功。
版本号机制
两种实现方案
乐观锁在进行写操作时,会判断是否能够写入成功,如果写入不成功将触发等待→重试机制,这种情况是一个自旋锁,简单来说就是适用于短期内获取不到,进行等待重试的锁,它不适用于长期获取不到锁的情况,另外,自旋循环对于性能开销比较大。
循环开销大
乐观锁
CAS,适用于写比较少的情况下(多读场景, 冲突一般较少)
synchronized,适用于写比较多的情况下(多写场景,冲突一般较多)
CAS与synchronized的使用情景
乐观锁/悲观锁
从线程是否需要对资源加锁
这三种锁是指锁的状态,并且针对synchronized
在Java 5通过引入锁升级的机制来实现高效synchronized。
这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。
降低获取锁的代价
偏向锁
当偏向锁的被另一个线程所访问,偏向锁就会升级为轻量级锁
其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
轻量级锁
指当锁为轻量级锁的时,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。
重量级锁会让其他申请的线程进入阻塞,性能降低。
重量级锁
synchronized 关键字之锁的升级(偏向锁->轻量级锁->重量级锁)
Java 1.6 为了改善性能, 使得 JVM 会根据竞争情况, 使用了这三种锁机制
这三种机制是根据竞争激烈程度进行的,在几乎无竞争的条件下, 会使用偏向锁, 在轻度竞争的条件下, 会由偏向锁升级为轻量级锁,在重度竞争的情况下, 会升级到重量级锁。
无法逆向
锁状态的切换
偏向锁/轻量级锁/重量级锁
从多线程并发访问资源(Synchronized实现细节)
公平锁/非公平锁
独享锁/共享锁
互斥锁/读写锁
可重入锁
锁的分类
锁
默认事务是可重复读
类型
原子性:指处于同一个事务中的多条语句是不可分割的。
一致性:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。比如转账,转账前两个账户余额之和为2k,转账之后也应该是2K。
隔离性:指多线程环境下,一个线程中的事务不能被其他线程中的事务打扰
持久性:事务一旦提交,就应该被永久保存起来。
特性
脏读:指一个线程中的事务读取到了另外一个线程中未提交的数据。
不可重复读:指一个线程中的事务读取到了另外一个线程中提交的update的数据。
幻读:指一个线程中的事务读取到了另外一个线程中提交的insert的数据。
隔离性问题
事务
Explain详解
详解:https://segmentfault.com/a/1190000039152427?utm_source=tag-newest
id
select_type
table
当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如 MyISAM、Memory,那么对该表的访问方法就是 system
InnoDB 表即使只有一行,也不是 system,而是 ALL
system
出现在主键和唯一索引,只有一条数据
只能是等值匹配(=),不能是in or < 之类的
const
联表的时候通过主键或唯一索引进行等值关联
eq_ref
普通索引进行等值匹配
ref
explain SELECT * from system_user WHERE dept_id =1 or dept_id is null
dept_id可以是普通索引,也可以是唯一索引
当对普通索引进行等值匹配查询,该索引列的值也可以是 NULL 值时,那么对该表的访问方法就可能是 ref_or_null
ref_or_null
index_merge
unique_subquery
explain select * from t1 where a<50 and a>20;
a是索引
如果使用索引获取某些范围区间的记录,那么就可能使用到 range 访问方法
range
查询所有的索引
explain select key_1 from t1
该联接类型与ALL相同,但index是只查索引。通常比ALL快,因为索引文件通常比数据文件小。都是读全表,但index是从索引中读取的,而ALL是从硬盘中读的
index
全表查询,很慢,应该避免
All
type
possible_keys 表示可能用到的索引有哪些
key 表示实际用到的索引有哪些
possible_keys / key
key_len 列显示 MySQL 决定使用的键长度。
如果键是 NULL,则长度为 NULL。
使用的索引的长度。在不损失精确性的情况下,长度越短越好 。
key_len
当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是 const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery 其中之一时,ref 列展示的就是与索引列作等值匹配的对象是啥。
如果不是等值查询,则显示为 NULL
简单理解:使用哪个列或常数与key一起从表中选择行
如果是全表查询,rows 列就代表预计需要扫描的行数;
如果使用索引来执行查询,rows 列就代表预计扫描的索引记录行数。
有可能是个精确值,也可能是个估算值
rows
显示 MySQL 在查询过程中的一些详细信息
Extra
Explain
如果对字段做了函数计算,就用不上索引了,这是MySQL的规定
对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能
防止条件字段函数操作
select * from t where id = 1
如果id是字符类型的,1是数字类型的,用explain会发现走了全表扫描,根本用不上索引
因为MySQL底层会对你的比较进行转换,相当于加了 CAST( id AS signed int) 这样的一个函数,函数会导致走不上索引。
防止隐式类型转换
like 'aa%'
ac也不走索引
最左匹配原则
一个表只能一个主键
不允许值重复或者值为空
主键索引
没有任何限制
普通索引
列的值必须唯一,但允许有空值
唯一索引
加索引
IN包含的值不应过多,能用between就不要用in
不要用*号
尽量用union all代替union
对于null的判断会导致引擎放弃使用索引而进行全表扫描
避免在where子句中对字段进行null值判断
SQL优化
字段的数据类型
数据类型的长度
MyISAM不支持事务,表级锁,但是查询速度快
InnoDB支持事务,行锁
合理利用表的存储引擎
优化数据库表结构的设计
将一张表的数据拆分成多张表
每张表的数量减少,查询速度相对会快一些
分表
避免一次处理太多的数据
移除不必要在事务中的select操作
大事务
例如最大连接数默认为100,即使SQL语句优化的再好,硬件设备配置再高,当请求超过100时都要再等待
数据库参数配置优化
通过使用MySQL主从复制,增删改操作走Master主服务器,查询走Slaver从服务器,这样就减少了只有一台MySQL服务器的压力
主从复制,读写分离
减少数据库连接
通过使用缓存服务器如redis、elasticsearch等增加缓存,减少数据库的连接
增加缓存层
更快的磁盘IO设备
更强的CPU
更大的内存
更大的网卡流量(带宽)
升级服务器硬件
优化
如果二叉树特殊化为一个链表相当于全表扫描了,影响性能
为什么不是一般二叉树?
每个节点只存一个键值和数据,如果是B+树,可以存储更多的节点,降低树的高度
为什么不是平衡二叉树?
B+树的数据存储在叶子节点,且数据是按照顺序排列,且有一条链表连着,那范围查找,排序查找,分组查找以及去重查找变得异常简单
为什么不是B树?
B+树非叶子节点不存数据,只存键值,而B树节点不仅存键值,也存数据。Innodb中页的默认大小是16KB,如果不存数据,那么就会存更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,这样一来,查找数据时,与磁盘的IO交互次数就会减少,效率就会提高
为什么要用 B+树,而不用其他树?
Innodb支持事务、行锁、外键,但MyIsam不支持
Innodb的的索引和数据是一起存储,但MyIsam是分开存储的
MyIsam引擎和Innodb引擎的区别
问题
MySQL
生命周期图
Thread的静态方法,释放CPU(bolcked),不释放锁
sleep
Thread的静态方法,释放CPU(runnanle),不释放锁
yield
Object类,释放CPU(等待blocked),释放锁,必须在synchronized语句块内使用,配合notify()和notifyAll()使用
wait
Thread的普通方法,抢占CPU,父线程等待子线程结束,会释放thread对象锁,但不会释放当前调用线程持有的对象锁(包括main线程),底层是wait方法,在JVM层面通过notify_all来唤醒
join
常用方法
1.corePoolSize:线程池中的常驻核心线程数2.maxinumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于一3.keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。4.unit:keepAliveTime的单位5.workQueue:任务队列,被提交但是尚未被执行的任务。6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。7.handler:拒绝策略,表示当队列满了并且工作线程-大于等于线程池的数量最大线程数(maxinumPoolSize)时如何来拒绝请求执行的runnable的策略。
ThreadPoolExecutor 7个参数
工作原理图
设计如此在创建新线程的时候,是要获取全局锁的,这个时候其他的就需要阻塞,影响了整体效率
阻塞队列的作用?为什么是先添加队列而不是先创建最大线程?
ExecutorService不会立即关闭,但不再接收新任务,在这之前提交的任务都会被执行,所有线程执行完成才会关闭,
shutdown
清空队列。但不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成
shutdownNow
shutdown和shutdownNow区别
线程池
线程
高频面试题
0 条评论
回复 删除
下一页