Java开发工程师真实面试流程及问题

1.平时常用的集合有哪些?

List下的ArrayList、LinkedList

Set集合中的HashSet

Map中比较常用的有HashMap,涉及到线程安全会用到ConcurrHashMap

2.先聊一下ArrayList和LinkList(分别说一下它们俩的使用场景)

首先ArrayList,它底层是一个数组,初始化的时候它的数据量是0,当它add的时候会变成10,扩容时每次扩容是它之前容量的1.5倍,它的特性是查询比较快,删除效率比较低

LinkList的底层是一个带有头结点和尾节点的双向链表,它提供了两种插入方式,一种是头插法LinkFirst,一种是尾插法LinkLast。它的特性非常适合经常增加、删除操作的场景,但是它在查询量大的时候会比较慢【问题3】

3.LinkList为什么查询会比较慢?

它在查询的时候因为它是一个链表查询,拿值时会按照顺序从第一个开始一个一个的进行比较【问题4】

4.ArrayList在查询的时候为什么就会很快?

因为它在查询的时候是根据下标去查询的,所以会很快【问题5】

5.当你遇到线程安全的一些情况下,你又想用List的情况下你会怎么用?

使用一个比较老土的Vector,它跟ArrayList一样底层都是一个数组。它与ArrayList有一点区别是它其中大部分方法都被synchronize关键字所修饰。所以说它是一个线程安全的,但它扩容的时候和ArrayList还是有一些区别的,它的扩容时2倍扩容【问题6】

6.刚才还提到了Map包下的HashMap,我们就常规的聊一下HashMap,说一下你知道的HashMap的一切

首先从它的底层结构说起,它的底层结构在1.7和1.8版有点不一样。它在1.7的时候底层的数据结构是一个数组加上一个单链表,在1.8的时候就改成了数组加上单链表或者说是红黑树的方式。当单链表和红黑树之间的转换,它的单链表的长度大于等于8的时候,并且它的hash桶大于等于64的时候它会将单链表转换为红黑树进行存储,它在红黑树的节点数量如果要是小于等于6的时候,它会重新转成一个单链表,这是它底层结构的一个变化,另外一点是关于它Hash桶的数量,它的数量默认是16个,它的阈值默认是0.75,还关系到它的扩容【问题7】

7.刚刚提到它的扩容,它具体是怎么扩容的?

扩容的时候它会首先检测数组里元素的个数,因为loadEactor(负载因子)的默认值为0.75,它含有的桶数量默认是16,它的阈值是16*0.75=12,当它哈希桶占用的容量大于12的时候,它就会触发扩容,它会扩容成之前哈希桶容量的2倍,它会同乘2的N次幂,会把之前哪些元素再次进行一次哈希运算,然后添加到新的哈希桶里面,再按照链表或者红黑树的方式再排列起来,这就是他的扩容机制。【问题8】

8.那你跟我说一下它是不是线程安全的?

它不是线程安全的,因为它在插入的时候多线程会有数据覆盖的可能,另外它在1.7的时候有个不安全的原因是:它在put的时候还有个resize的过程,这个过程会造成它的头插(因为它1.7的时候用的是头插法,会形成一个环形链表导致一直死循环),在1.8的时候它改成尾插法。【问题9】

9.它正常的替代是什么,或者说我们如何保证它的线程安全?

平时会直接使用ConcurrentHashMap替代来保证它的线程安全【问题10】

10.还有HashTable或者Collection.Synchronized来替代,为什么选择了ConcurrentHashMap?

只是通俗意义上觉得ConcurrentHashMap常用一些(可以从他们之间的一些差距、机子的性能方面了解)【问题11】

11.那跟我聊一下ConcurrentHashMap

首先它的数据结构在1.7版底层是一个分段数组,为了保证线程安全它有一个Segment(分段锁),这个Segment继承于ReentrantLock,来保证它的线程安全,它每次只给一段加锁来保证它的并发度,另外在1.8的时候它改成了和HashMap一样的数据结构,在1.8版它会逐渐放弃这种分片锁的机制,而使用了Synchronized和CAS来操作,因为在1.6版的时候JVM对Synchronized的优化非常大,现在也是用这种方法保证它的线程安全【问题12、13】

12.刚刚你提到1.7版之前Segment分段锁,它如果要找到具体的值它会经过几次哈希吗?或者它是如何去找到一个具体的数值?

.......

13.刚才说1.8版之后它是CAS加synchronized,那你说一下CAS是什么?

CAS相当于一个人轻量级的加锁过程,比如说你要修改一个但是在并发量不是特别大的情况下,锁竞争不激烈,你要修改个东西,你要先查,查完之后再修改,修改完,在准备写之前它会再查一次,比较之前的结果有没有区别。如果有区别说明这个修改是不安全的,如果没有区别说明这个修改是安全的。这时它就可以安全的去修改,而不是直接加锁的那种形式,在低并发的情况下性能会好一点的。理解的时候见名知意CAS--Compare And Swap比较并替换。CAS 是一种更新的原子操作, 比较当前值跟传入值是否一样,一样则更新,否则失败【问题14】

14.它有什么优点和缺点?

优点:它比较轻量级

缺点:在高并发情况下,它始终都有个忙循环的过程,对CPU的性能消耗比较大,当高并发的时候建议直接使用状态机、锁之类的,另外它可能会产生一个ABA的问题,之前读和再过段时间读中间会被第三个人修改过,但是它又给改回来了。这个问题可以通过添加一个戳或者标志位来解决,保证它中间没有被篡改过,就相当于加版本号、标志之类的。【问题15】

15.刚刚提到了synchronized再聊一下synchronized

synchronized可以用在比如说同步代码块,还有它同步代码块是可以指定任意的锁、对象作为锁,当它在应用于方法的时候,它的锁就是锁this,如果是用静态方法则锁定的是它的class对象。关于synchronized在JDK1.6升级还是比较大的,因为它的锁是一个种类,首先是无锁状态,因为它的偏向锁,然后到轻量级锁,最后到重量级锁。一般在偏向锁的情况下,它偏向于第一个获得锁的线程,它会将线

程拉到这个锁对象的对象头当中,等其他线程来的时候,它可能就会立刻结束这个偏向状态,进而跑到一个轻量级锁,轻量级锁也是在

低并发的情况下,来消除锁的源于,它主要在虚拟机栈中开辟一个空间,将锁对象的Mark Word写入,再尝试将另一个Lock Record的指针使用CAS去修改锁对象头的那个区域,完成加锁的过程。它也是普遍应用于一个低并发的情况,再往上如果锁竞争非常激烈,那

就回立刻膨胀为一个重量级锁,那用的是一个互斥锁的过程。它加锁过程的主要实现原理是在同步代码块中它会在你的代码块前后加上两个指令,一个是mointerenter,另一个是mointerexit,当一个线程来时,它发现这个对象头中它的锁标志位是无锁,应该是01的状态,它会尝试给一个互斥锁对象,这个锁对象的时候会跟另外一个对象关联,就是监视器锁monitor,它会将monitor的一个锁定器加1并且将monitor的指针写入到一个对象头中表示,并且修改它的锁对象标志位为10,就是他重量级锁的一个标志位,以此完成换锁的过程,并且它的这个过程是可重入的,因为它不会每次出去需要加锁还要再释放锁,它每次进来后获取这个锁,让锁记录加入即可,它加锁完之后,当其他线程来的时候,它会检查到这个锁对象头中,monitor监视器锁上计数器不为0,它会在monitor监视状态下等待区竞争这个锁,如果结束了之前的操作,他就退出开始释放这个锁,并且逐步的将加上的锁定释放几次,让计数器清零来完成对锁的释放,让其他线程继续去竞争这个锁,这是它重量级锁同步代码块的一个原理。在同步方法中它是一个ACC——SYNCHRONIZED标记位,相当于一个flag,它自动去走一个同步方法调用的策略 ,这个原理是比较简单的【问题16】

16.什么时候用synchronized和ReentrantLock有考虑吗?

它们俩的区别还是比较大的,从JVM层面上的Synchronized是JVM的一个关键字,ReentrantLock其实是一个类,需要手动编码,像synchronized使用时候是比较简单的,我直接通过同步代码块或者同步方法,也不需要关心锁的释放。但是ReentrantLock我需要手动去Lock,然后配合try finally代码块一定要去把它的锁给释放,另外ReentrantLock相比synchronized有几个高级特性,它提供了一个像如果有一个线程长期等待不到一个锁的时候,为了防止死锁,你可以手动去调用一个locklnterruptibly方法,尝试让它释放这个锁,释放自己的资源不去等待,然后ReentrantLock它提供了一个可以构建公平锁的方式,因为它的构造函数有一个但是不推荐使用,它会让ReentrantLock等级下降,此外它还提供了一个condition,你可以指定去唤醒绑定到condition身上的线程,来实现选择性通知的一个机制,这是它们之间的一个区别,关于它的选择性,如果你不需要ReentrantLock以上的三种特性,还是一定要使用synchronized,因为相比来说它是JVM层面的关键字,当优化JDK的时候它会非常方便的去了解当前的锁被哪些线程所持有,这些状态不是ReentrantLock能相比的,但是还是要选择场景使用,任何事情不是唯一的,在对应的场景选择使用相对应的锁。【问题17】

17.刚才提到了ReentrantLock,请你说一下ReentrantLock里面是如何实现的,比如像它里面的公平锁非公平锁,底层的AQS?

18.在JUC下面你还用过哪些包?

CountDownLatch、CyclicBarrier和Semaphore

CountDownLatch适用于单线程在运行时有一段过程你又希望并发的去执行,后来又回归到一个单线程状态。它适合一个线程等待一批线程达到一个同步点,之前进去就行,它的计数器是不能重用的。

CyclicBarrier 它是一个类似CountDownLatch的,但是它没有一个线程一个线程的等待,它是一批线程同时到达一个临界点之后再往下走,它的好处是当你CountDownLatch,它的计数器是可以留下来的【问题19】

19.CountDownLatch是如何实现计数功能的?

思路:ReentrantLock中的AKS里面有一个state标志位,用volatile去修饰的【问题20】

20.了解过volatile吗?

volatile是JVM提供的最轻量级的一个关键字。说volatile首先要说到计算机的模型,CPU和内存之间的线程效率是差好多数量级的,但是为了保证它们之间的计算 ,不影响CPU的计算,然后中间有好多LLV那种缓存,我们线程就是在这种缓存中去工作,首先它取数据会从主内存取到工作内存中,在工作内存中计算完之后再传过去,这时候就会有一个问题,多个线程之间的可见性是如何保证的,在计算机层面是有好多协议,在JVM的时候它为了解决这些比较复杂的东西,它提供了像JMM的这种模型,像被volatile修饰的一个变量,它就可以保证这个变量在所有线程间的可用性,在修改这个变量之后可以刷到主内存(总线嗅探机制),它在使用时会立刻从主内存中取出来刷新那个值,volatile它是不能保证变量的原子性,像自增自减这种操作它是不能保证的【问题21】

21.那么原子性我们是怎么去保证的?

可以使用synchronized还有ReentrantLock通过加锁来保证原子性

使用final他也是使用了这个

并发包下面还有像Atomic、AtomicInteger底层也是差不多

22.你知道不断的去总线嗅探会有什么问题吗?

会导致它总线占用的资源就很大(总线风暴)----了解

23.多线程的线程池了解吗,说一下线程池的运行机制?

线程池它有一个核心线程数,当你的线程在运行的时候,如果你没有设置成预启动加载它的首发线程数为0,当你提交一个新任务的时候它会首先是建立一个核心线程去执行任务,那此时如果你要是一直来任务它之前的有没有执行完那么你会一直建核心线程,当达到最大值核心线程数时,如果还都在忙,那么就会放到BlockingQueue里面作为节点,如果BlockingQueue队列也放满了而且核心线程都在忙,那就会去建立新线程,它叫作非核心线程,若一直创建,数量达到非核心线程数max access就会触发一个拒绝策略。JDK内置了四种拒绝策略,第一种是AbortPolicy,直接抛出异常来解决;第二种是DiscardPolicy,悄无声息的丢弃你的任务;第三种是DiscardOldestPolicy,丢弃你最早未执行的任务;第四种是CallerRunsPolicy,谁调用我的这个线程去执行你的这个任务,它这种方式而是会影响你的新任务提交速度,关于使用的队列,它是阻塞队列,JDK提供了两种,第一种是不保存任务的那种,JDK提供的new catch线程池是使用这种队列,第二种是有界队列ArrayBlockingQueue,你指定数量要是超了它可能会OOM的,第三种是无界队列ArrayBlockingQueue,它可能因超出上下文二OOM的,最后一种是优先级队列。关于线程池还有一个比较重要的参数线程构造,在创建一个线程池的时候你要提供一个线程的threadFactory,一定要指定它的名称,这是很重要的一点,并且你也可以设置它为守护线程,当你BM关闭的时候可以让线程跟着他一块消亡【问题24】

24.你刚提到了ArrayBlockingQueue、LinkedBlockingQueue你了解它们的底层吗?

它们底层好像是AQS做的......

25.说一下平时的SQL调优思路

 SQL调优最基本的就是表要有主键,因为有主键的表MySQL会创建聚簇索引,聚簇索引的好处是它的主键和数据是在一行的,当你在explain查询语句的时候会发现它的type级别是const,这是很高的一种级别,当有主键的时候,如果一条SQL语句查询很慢,可以去看它是否建立了相应的索引,而建立索引的时候要尽量选择where条件后面的子段或者是group by或者是join连接的子段作为你的索引列,这些索引列也要排个序,要符合最左匹配原则。你选择的时候要根据它们的索引选择器,你的非重发的数据行和重发的数据行中间的排列,大的放左小的放右这样的形式,并且这种多列的索引列你要去建立联合索引而非单个索引,这是索引的选择。在SQL书写的时候不要将索引放到一个表达式中或者是反向判断比如NotNull、不等于、Notln等这种关键词,它会让你的索引失效。如果你的数据查询非常频繁你可以考虑给它使用那种覆盖索引,覆盖索引是可以直接在索引中查询到数据的,相对来说还是很快的,如果你的索引设计不生效你要可以去考虑下是不是MySQL因为一些其他的原因造成的。因为MySQL的底层它有一个随机采样,它会根据你的索引基数,但不可能全都给你标记上,但它会根据随机采用来计算你的索引基数是如何的,如果它采错了它就会认为虽然你的索引选择性比较大,但它认为你的索引选择性比较小,就可能不走你的索引,可以通过加force index强制让它走该索引,如果不行你需要刷新下它的信息,要用analyze TABLES,看它有没有在重新组队。【26】

26.在创建索引时是创建唯一索引好点还是普通索引好点?

聚簇索引,它确切的来说应该是一种数据结构,它是将它的主键和数据行放在一块的,它是通向真实数据行的有利途径 ;而非聚簇索引就是我们常说的普通索引 ,它的索引是放叶子结点和他的主键和它的索引列,在查询的时候其实是通过它的索引列查询到它的主键,再通过主键去回表查询,还是通过一个聚簇索引去查的,只不过它有一个回表的过程速度相对来说会慢一点。

唯一索引它会保证列值的唯一性,会有一个判断的过程,但这个判断的过程开销是很小的,正真的开销是在它的buffer区的。

27.MVCC和事务隔离级别的关系

事务本身是靠MVCC去保证的,首先InnoDB的特性就是支持事务,在支持事务的同时为了保证并发度,它提供了一个隔离级别,它有四种隔离级别分别是未提交读、提交度、可重复读、可串行化

28.spring是怎么做到事务隔离级别的?

它提供了一个注解叫Transactional,并且它提供了一个事务传播行为

29.spring的AOP是是如何实现的?

 

 

 

 

关于Redis常见面试题

业务场景: 高并发环境下.用户长时间对服务器进行操作,可能产生如下的问题.

什么是缓存穿透

说明: 用户高并发环境下访问数据库缓存中都不存在的数据称之为缓存穿透现象.

在这里插入图片描述
解决方案:
1). 禁用IP 限制IP访问.
2). 限流 每秒最多访问3次
3). 布隆过滤器

布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

原理:
在这里插入图片描述
布隆过滤器优化:
问题:如何解决hash碰撞问题
知识点: 由于hash碰撞问题,可能由多个key有相同的位置,所以得出结论,布隆过滤器认为数据存在,那么数据可能存在.如果布隆过滤器认为数据不存在,则数据一定不在.

如何降低hash碰撞的几率:
答:
1.扩容二进制向量位数.
2.增加hash函数的个数
当位数增加/函数适当增加,则可以有效的降低hash碰撞的几率. 默认值 0.03
在这里插入图片描述

2.3.2 什么是缓存击穿

说明: **某个(一个)热点数据在缓存中突然失效.**导致大量的用户直接访问数据库.导致并发压力过高造成异常.
在这里插入图片描述
解决方案:
1.尽可能将热点数据的超时时间 设定的长一点
2.设定多级缓存 超时时间采用随机算法。

2.3.3 什么是缓存雪崩

说明: 在缓存服务器中,由于大量的缓存数据失效,导致用户访问的命中率过低.导致直接访问数据库.
问题分析:
1. fluashAll命令可能导致缓存雪崩.
2. 设定超时时间时,应该采用随机算法
3. 采用多级缓存可以有效防止.
在这里插入图片描述

 

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值