JAVA面经复习(十二)

面试难度:<mark>☆☆☆☆</mark>

问、 栈中取最小值,时间复杂度低 O(1)

答:双栈结构,时间复杂度应该指的是查询。维护两个栈,一个是最小栈,一个是记录最小栈元素下标的栈。

问、hashmap 原理,线程为什么不安全 ,有哪些不安全的地方

答:jdk在1.8中,hashmap采用数组+链表和红黑树的结构,HashMap允许有一条记录的key为null,但是对值是否为null不做要求。其负载因子为0.75,默认初始长度为16。在达到12的时候,会进行扩容,大小变为原来的两倍。
线程不安全:
1、首先是在put的过程,会出现数据不一致的情况。假设当前有线程A渴望插入,其找到了对应的哈希桶,同时获得了链表头结点。但这个时候可能发生线程切换,获得资源的B线程也要插入数据,且在A的同一个位置进行了插入,而且完成了插入。等到A重新拿回临界资源的时候,A用了原始的链表头进行插入,致使B的插入数据消失。
2、在resize的情况下,可能会产生死循环。我们假设有两个线程同时需要执行resize操作,我们原来的桶数量为2,记录数为3,需要resize桶到4,原来的记录分别为:[3,A],[7,B],[5,C],在原来的map里面,我们发现这三个entry都落到了第二个桶里面。

假设线程thread1执行到了transfer方法的Entry next = e.next这一句,然后时间片用完了,此时的e = [3,A], next = [7,B]。线程thread2被调度执行并且顺利完成了resize操作,需要注意的是,此时的[7,B]的next为[3,A]。此时线程thread1重新被调度运行,此时的thread1持有的引用是已经被thread2 resize之后的结果。线程thread1首先将[3,A]迁移到新的数组上,然后再处理[7,B],而[7,B]被链接到了[3,A]的后面,处理完[7,B]之后,就需要处理[7,B]的next了啊,而通过thread2的resize之后,[7,B]的next变为了[3,A],此时,[3,A]和[7,B]形成了环形链表,在get的时候,如果get的key的桶索引和[3,A]和[7,B]一样,那么就会陷入死循环。
如果在取链表的时候从头开始取(现在是从尾部开始取)的话,则可以保证节点之间的顺序,那样就不存在这样的问题了。这也就是JDK1.8中hashmap不再采用头插法的原因。

问、 hashmap 死循环?

答:从上我们可以了解到,hashmap的死循环主要是由于其多线程下的头插法引起的,因此在JDK1.8抛弃了头插法,而是采用了尾插法的方式。

问、 MySQL 为什么用了索引会让查找速度变快

答:因为索引(以Innodb为例)采用B+树对数据进行存储,而B+树的索引时间复杂度为O(logn)相比较原先的O(n)逐个索引速度肯定更快。

问 、 MySQL 优化有哪些 ;

答:(一) 使用查询缓存优化查询
(二) 使用EXPLAIN关键字检测查询
(三)当只要一行数据时使用LIMIT 1
(四)为搜索字段建立索引
(五)在join表的时候使用相当类型的列,并将其索引
(六)切记不要使用ORDER BY RAND()
(七)避免使用SELECT *
(八)永远为每张表设置一个ID主键,以便使用聚簇索引。
(九)使用ENUM而不是VARCHAR
(十)选择正确的存储引擎
(十一)尽可能的使用NOT NULL,避免索引失效。
(十二)用exists去替代in,在大表下搜索效率更好。

问、 CAS算法 机制;

答:CAS属于乐观锁,其方式是通过获取一个数据A,并查询获得的数据A与查询到的数据A是否一致。如果一致就对数据进行修改,如果不一致则说明此时有别的线程在使用该临界资源,并进入自旋。

参考资料:
2021.2.26 松果出行 Java开发一面
为什么HashMap线程不安全