前言
坐标北京,两年java经验,看书看博客当时理解的知识点,过一会儿或者在面试的时候很难给面试官讲清楚,一是忘的快,二是表达能力欠佳。故在博客上记录下来,提高对面试知识点的印象和逻辑表达能力。ps:记得的都写下来了。
正文
面试官:synchronized和ReentranLock有什么区别?
答:synchronized是java语言层面提供的一个关键字,而ReentranLock是java的一个类,此外的话,ReentranLock它可以用Condition对象创建多个等待队列,而synchronized锁定的对象只有一个等待队列。ReentranLock比较灵活,可以响应中断,而synchronized里的代码要么是发生异常了要么是全部执行完才能退出,ReentranLock支持等待一定的时间。
面试官:HashMap的原理是什么?
答:HashMap它的底层数据结构是数组+链表,我们再调用它的put方法的时候,首先,它会根据我们传进去的key的hashCode()方法得到一个hash值,然后根据这个hash值通过一个数组下标定位算法定位到数组位桶的下标,在取得该位置的第一个元素进行比较,先比较他们的hash值是否相同,若相同,才继续判断是否equals,否则继续判断下一个node。当没有node相等的时候,会在该位置新建一个node,并把next指向之前的那些node节点,使发生hash冲突的元素保持链表的结构(jdk1.8中当碰撞node数达到默认的8,会将链表变为红黑树,从而提高查询的效率),最后判断时候达到初始hashEntry数组的规定容量(默认16*0.75),达到,则扩容。
追问:你刚刚说了负载因子,它有什么用呢?为什么要设置成0.75呢?
答:负载因子的话,主要目的还是为了避免hash碰撞嘛,设置成0.75的话,这个是jdk的设计者们研究出的一个数值,它能使时间和空间达到一个很好的平衡嘛。其余就不知道了。。。
面试官:HashMap是线程安全的么?
答:不是
追问:那有线程安全的map吗?
答:有,HashTable和ConcurrentHashMap。
追问:他们有什么区别呢?
答:首先,HashTable的定义是同步容器类,而ConcurrentHashMap定义则是并发容器类,从定位上来说就可以看出两者在并发环境下的效率;HashTable它里面的方法基本都是以synchronized修饰的,也就是说同一时刻只能有一个线程能访问HashTable,在高并发环境中效率非常低下。而ConcurrentHashMap它可以允许多个线程同时读,一定数量的线程同时写(默认并发度16,即默认下最多可以允许16个线程同时执行put方法)
追问:那ConcurrentHashMap是怎么实现的呢?
答:它的实现的话主要是使用了分段锁的机制,它的内部有一个叫segment的内部类,这个内部类是继承于ReentranLock的,也就是相当于一把锁了,ConcurrentHashMap内部维护了一个segment数组。我们调用它的get方法的时候他是不会上锁的,这个实现就能达到允许n个线程同时读数据,我们调用它的put方法的时候,才会去上锁。那它默认de的segment的数组的长度是16,也就是说,默认情况下,我们最多可以有16条线程同时执行put方法(16条线程均匀的分配到了16个segment上)。
追问:你这是jdk几的实现?
答:jdk1.6.
面试官:哦哦,jdk8不是这么实现的哈。
我:emmmm。。。
面试官:你刚刚提到了ReentranLock,你能给我讲一下它是怎么实现的么?
答:ReentranLock的话它的实现是基于AQS完成的,其实AQS是java中一个非常重要的类,理解了它的话基本其他的一些同步组件类都很容易弄懂了。那,AQS的话呢它主要有一个状态变量State,用于给不同的同步组件实现各自的一个同步语义,比如说ReentranLock用这个state来表示线程获取到的锁的次数,那在我们调用它的lock方法的时候呢,它会尝试着以cas的方式(AQS提供的方法)把这个state的值从0变成1,如果改变成功了,那么就代表当前线程获取锁成功了,然后他会设置当前线程为状态的持有者,以支持重入。如果改变失败,那么就代表别的线程获得了锁,但是会继续判断那个获得了锁的线程是不是我自己。如果是我自己,就继续把state的值加1,如果不是,就进入AQS维护的同步队列中等待。我们调用它的unLock方法的时候则是会把state值减1,减到0的时候,其他的线程就又可以竞争这个state了。
面试官:恩,看来是看过一些源码的哈,java中读写锁实现太麻烦了,你能不能自己设计一个读写锁呢?有思路么?
我:emmm。。。。不同清楚。。
面试官(微笑):可以用信号量和一些其他的同步组件去实现的哈。(问题过。)
面试官:事务了解吗?ACDI是什么?
答:acdi就是事务的四个特性嘛,原子性,一致性,隔离性,持久性。其中针对隔离性数据库会提供4个隔离级别来解决一些问题,那首先我们得知道若没有隔离级别,会有什么问题。
1.脏读,脏读就是一个事务读到了另一个事务未提交的数据,比如我事务b把一个数据从1改成了2,这时,我事务a读到了这个数据为2,但是事务b由于某种原因,回滚了事务。此时脏读就产生了。
2.不可重复读:不可重复读就是指在一个事务范围内,两次或多次查询数据期间,其他事务将数据改了,导致前后多次查询数据的结果不一样。比如事务a开始查询一个数据为1,事务b把这个数据改为了2,这时事务a再去查询数据为2,前后两次查询同一个数据,结果不一样。
3.幻读:幻读的重点在于insert操作,比如事务a现在要把一张表中50个数据(性别为男)改为女,在事务a修改的期间,事务b向这个表中插入了一条数据(性别为男),ok,然后事务a想查询一下修改的结果,却发现还有一条数据未被修改过来。就像发生了幻觉一样,这就叫幻读。
4.丢失更新:比如事务a先将数据1改成了2,这时,事务b又将数据2改成了3,然后事务a去查询数据,发现自己做的修改没了,查询到的数据是3.这就是丢失更新
面试官:那事务的隔离级别是怎么实现的呢?
答:是根据行锁来实现的,数据库中按粒度来分的话,有表锁和行锁,按模式分又有共享锁和排他锁。我发现一个特点,就是支持行锁的数据库存储引擎也支持事务,不支持行锁的存储引擎,不支持事务。那么,行锁是怎么实现隔离级别的呢?
1.在未提交读这个隔离级别下:
事务在读数据的时候,不上锁
事务在写数据的时候,会为数据上一个行级的共享锁,直到事务结束释放。
2.在提交读这个隔离级别下:
事务在读数据的时候,会为数据上一个行级共享锁,但是读完立即释放,锁持有的时间短;
事务在写数据的时候,会为数据上一个行级排他锁,直到事务结束后释放。
3.在可重复读隔离级别下:
事务在读数据的时候,会为数据上一个行级共享锁,直到事务结束后释放,锁持有时间长;
事务在写数据的时候,会为数据上一个行级排他锁,直到事务结束后释放。
4串行隔离级别下:
读数据上表级的共享锁,写数据上表级的排他锁。
ps:这个回答只是理论上来说的,各个数据库的实现都很复杂,我也是面试完之后再回来好好看了一下的。但是面试的时候回答到这些已经不错了,无奈面试官懂的太多了,他还是继续往下问
面试官:你刚刚说事务是为数据上的锁,你确定吗?
我:你都这么问了,应该不是吧。。。
面试官:事务是为索引加的锁哈。mysql默认的隔离级别能解决幻读问题么?
我:它可以解决不可重复读和脏读,但是不能解决幻读。
面试官:你确定???
我:emmm。。。。。确定。
面试官:是可以的哈,用了间隙锁。
ps:回去一查,确实是可以解决的,其实仔细想想也知道,如果问题都没法解决那为什么还要默认使用这个级别呢?
面试官:mvcc知道么?
我:不太清楚。
面试官:你还知道数据库的那些知识?
我:索引吧,我知道索引它是使用的b+树的形式,至于为什么要使用这种数据结构,主要是为了减少磁盘io次数,因为数据库的瓶颈就是磁盘的io嘛。而b+树的结构符合那种矮矮胖胖的形式,高度很低,那树的高度低的话,磁盘的io次数也就低了。
面试官:b+树是二叉树么?
我:不是,它是一种多叉树,如果是二叉树的话,它的高度就很高了。
面试官:索引是顺序的还是乱序的?
我:emmm。。。应该是顺序得把。
面试官:恩,因为在磁盘中,顺序的查找只需要沿着那个啥往下找就行了,而乱序的就是一下崩这里,一下崩哪里。
我:哦。。。。明白了,明白了,长知识了。。
面试官:b+树是怎么排序组合索引的?比如我建了一个组合索引(A,B)我按B的排序去找能找到吗?
我:不能找到,要按照最左匹配原则查找。
面试官:那最左匹配原则在b+树上是怎么样的呢?比如现在有A 1、2、3 ,B 1、2、3 ,在b+树上他是怎么排的?是A1、A2、A3 还是B1、B2、B3这么排的?(脑补吧哈哈)
我:应该是A1、A2、A3(瞎说的,我都不明白他说的啥)
面试官:恩,你再做几道题,待会儿还有个二面哈。
二面。卒,全是问的一些项目的,包括一些分布式的东西,我说上家公司没接触过分布式的一些东西,但是我也有了解,最后,回去等消息吧。。。我们内部再合计合计。。。
写在结束
现在大多数公司都是要求分布式啊,微服务啊相关的技术人才,比如dubbo 、zookeerper、springBoot、springCloud、redis、mq等等,自己也确实缺乏这些方面的认识,仅仅停留在了解,知道他们是干嘛的,解决了哪些问题。实际项目中并没有使用过,也不知道其实现原理。这次面试,虽然没过,但是和一面的技术聊的蛮开心的,在一些问题的深度上面还有待提高。把自己还记得的一些问题记录下来,加深一些印象,期待下一次面试。溜了溜了