面试官这么问你,你如何回答?

勾勾收罗了小伙伴们的面试题目,把并发编程的面试题目总结如下,作为大家面试的参考。

Q:线程的状态有哪些?

线程的生命周期大致分为5个阶段:

NEW(新建):新建一个Thread对象时,此时还没有线程。

RUNNABLE(就绪):调用start方法可以使线程进入就绪状态。

RUNNING(运行):处于就绪状态的线程获得了CPU就可以执行业务,进入运行状态。

BLOCKED(阻塞):处于运行状态的线程如果调用了sleep、wait方法或者竞争锁失败会进入阻塞状态。

TERMINATED(终止):线程正常结束或者意外终止会进入终止状态。

线程的生命周期是比较基础的知识,一般是作为一个切入点有浅入深的问其他问题,在回答这个问题的时候不要干巴巴的只说5个状态单词,要把每个状态是怎么进入的也要讲一下。你如果不讲,面试官就会继续追问:那线程在哪些情况会进入阻塞状态呢?

这个题目是一个小伙伴的阿里一面的题目,还是比较简单的,大家可以按照自己的理解总结词汇。

上面有说到竞争锁失败会使线程进入阻塞状态,那么就可以引出下一个问题。

Q:synchronized锁的用法?

synchronized是JVM关键字,可以用来修饰代码块、方法(普通方法和静态方法),它是一个可重入的不可打断的锁。

这个时候面试官就可能继续追问:多线程竞争的对象是什么?

线程竞争synchronized锁其实是对锁对象monitor的竞争,代码块是Object锁对象的monitor,方法是this monitor,静态方法是class monitor。

synchronized的问题绝对不会到这里就结束,大部分的面试题都是想问synchronized锁的状态,有的面试官直接问锁的升级机制,有的会先看下你对synchronized的掌握再问你锁的状态。所以大部分面试官接下来会问:JDK1.5之后锁的状态有哪些?

synchronized在JDK1.5时锁竞争失败后会直接进入阻塞状态,线程上下文之间切换比较消耗CPU性能,在JDK1.5之后对锁进行了优化,引入了无锁,偏向锁,轻量级锁和重量锁。锁的升级过程即是无锁状态到重量级锁状态的变化过程。

锁对象头(Mark Word)有一个偏向标志,无锁状态时为0,当线程获取到锁时会把偏向标志修改为1,并且CAS把对象头中的线程ID修改为自己的线程ID,当下一个线程竞争锁时,发现偏向标志为1,且锁还未释放,则会复制对象头的信息到自己线程的Lock Record中,并且CAS修改对象头中的指针指向自己的LR,如果修改失败则自旋一定次数后(默认10次)升级为重量级锁,此时竞争失败的线程将会挂起。

此题目在面试中出现的次数比较多,小伙伴们在涂鸦智能,阿里二面,金山面试中都有出现。

Q:synchronized和lock的区别?

两个锁之间的区别从特性、用法、性能和高级特性上说。

  • 特点:synchronized是独占可重入锁,是非公平的竞争锁方式。ReentrantLock也是独占可重入锁,但是其可以指定为公平锁,默认是非公平锁。

  • 用法:synchronized可以修饰方法和代码块,不需要显示的加锁和解锁。ReentrantLock修饰代码块,在lock()和unlock()方法中间的代码都是同步代码,需要显示的加锁和解锁,将锁的控制权交给了开发人员。

  • 性能:基于JVM对关键字的支持,单线程下synchronized关键字性能要优于ReentrantLock,但是多线程环境下ReentrantLock性能优于synchronized。

  • 高级特性:获取synchronized锁失败的线程会一直阻塞直到获取到锁,不能中断。ReentrantLock提供了可中断获取锁的方法lockInterruptibly(),而且还提供了获取锁失败不阻塞立即返回的方法tryLock(),如果开发场景中涉及到了高级应用,那就只能选择显示锁Lock了。

此时面试官会继续追问:你在开发中是如何选择的?

我们在回答这个问题的时候要分析自己的业务场景,在多数情况下会优先选择synchronized关键字,在必须要用到高级特性的时候选择Lock锁。另外一点就是要注意synchronized锁升级的不可逆性,并发量谷峰值差别较大的时候优先选择Lock锁。

这个问题是一个小伙伴没有透露面试公司的题目。

Q:开发中你是怎么创建线程的?

这个问题勾勾是觉着没必要把创建线程的4种方式答出来,真正开发中使用的肯定是线程池,面试官通过这个问题就是想问你线程池的原理。

此时你只需要简单的回答,使用线程池的方式管理线程,并且线程的参数都是可配置的。

这个时候面试官就可以继续问你:线程池的参数有哪些?

corePoolSize:核心线程数量,即使线程池中的线程空闲也会一直保持此数量的线程,除非设置了allowCoreThreadTimeOut为true。

maximumPoolSize:线程池中允许的最大线程数量。

keepAliveTime:当线程池中的线程数量超过了核心线程的数量,在回收空闲的线程时线程将等待任务的最大时间,超过这个时间还没有任务执行,那么线程将会被回收。

unit:超过核心线程数量的线程空闲时等待任务的时间单位。

workQueue:用于存放提交任务的工作队列,这个队列只存放提交但是未执行的Runnable。

threadFactory:创建线程的工厂。

handler:当线程数量和队列元素都达到最大时,拒绝再次提交任务的策略。

有赞的一次面试中小伙伴被问了一个问题:线程池中的线程是谁创建的?

提交任务的主线程创建的,这个勾勾在写线程池原理的时候有刻意提到过,如果知识点比较模糊真的可能被面试官问懵了。

面试官还继续追问了:拒绝策略有哪些?开发中你是怎么选择的?

拒绝策略有4种:直接丢弃,丢弃队列中第一个任务,抛出异常,调用者执行。

前两个开发中一般较少选择,因为不可控。开发中勾勾用过后面两种:抛出异常捕获了记录日志。让主线程去执行保证任务一定被运行。

Q:开发中使用多线程要注意什么?

多线程的注意事项肯定死锁排在第一位。你回答了死锁面试官就可能继续追问:什么情况下会发生死锁?

什么情况下会发生死锁呢?交叉锁互相等待肯定会死锁,还有比如内存不足导致线程无法执行,死循环导致的锁无法释放,显示锁开发人员忘记释放锁也会导致死锁。

问到这里可能面试官真的就多嘴一问:你们项目中遇到过死锁的情况吗,线上是怎么排查的?

死锁的发生可能会导致CPU飙高,而且你可能根本就看不到堆栈信息,是比较难排查的。

勾勾这周专门写一篇文章分析。

这个题目是阿里一面的题目。

Q:什么是乐观锁和悲观锁?

乐观锁和悲观锁都是一种思想,有对应的实现。乐观锁主要用于多读的场所,悲观锁主要用于多写的场所。

悲观锁:在修改数据之前先加锁,再对数据进行修改的加锁方式。在数据修改的整个过程中都会加锁。悲观锁又分为读锁和写锁。

乐观锁:在修改数据前不需要加锁,只有在对数据进行修改的时候才会进行检测。可以利用CAS实现乐观锁。

面试问这个问题一般是想问CAS的。

CAS比较并交换,但是会出现ABA的问题,如果不能接受ABA那么在比较的时候可以添加版本号。

此题目为阿里一面、金山、和某个不知道公司名字的面试题。

Q:volatile关键字的作用?

volatile关键字是一个轻量级锁,它可以保证可见性和有序性,但是不能保证原子性。

此时面试官可能会问:它是如何保证可见性和有序性的,为什么不能保证原子性?

读volatile修饰的变量时,会从主内存中取数据,然后在线程的工作内存中创建变量副本。写volatile修饰的变量时,会对总线lock加锁,此时其他CPU都不能访问到这个变量,当线程将修改后的数据写入主内存并通知其他CPU的数据失效后,对总线解锁。其他线程在后续的过程中因为变量失效不得不从主内存再次获取数据,从而保证了可见性。

有序性就是通过内存屏障实现的。

详细的了解大家可以看勾勾JMM内存模型的文章。

此题目为涂鸦智能的一面。

Q:ThreadLocal原理是什么?

ThreadLocal可以实现线程之间的分离,ThreadLocal修饰的变量,每个线程都会复制一份,因此常用来修饰静态变量。ThreadLocal内部维护了Entry数组,其中key是当前线程,Value是用户存入的变量。

ThreadLocal内部类Entry继承了弱引用,key是weak的引用,一旦GC不管内存是否充足都会被回收,但是value是强引用,会存在null的key指向value,因而造成内存泄漏。因此使用结束需要手动的remove,避免内存泄漏。

ThreadLocal是特高频面试题目,阿里、网易、有赞、涂鸦、其他等等公司都有问。

Q:AQS的原理是什么?

AQS是JUC工具类的核心,它内部维护了volatile的状态变量state,锁的竞争即是对state的竞争,竞争失败的线程会加入阻塞队列,它是一个先进先出的队列。AQS提供了不同的获取锁和释放锁的操作,包括独占模式(公平和非公平的获取锁方式),共享模式,条件等待模式。线程的node节点对象维护了4个状态:CANCLLED、SIGNAL、CONDITION、PROPAGATE ,决定了线程是否需要挂起和唤醒。

AQS原理的东西其实是比较多的,回答时候可以先把几个关键点描述清楚,重点就是看面试官如何展开了。

不想被面试官吊打,最好的办法就是弄懂它的原理。勾勾也是针对AQS写了好几篇文章,原理和实现类的都有,仅供参考喔。

Q:场景:如何实现三个线程顺序执行?

AQS实现类的用法一般都是给你描述个场景问你如何实现,比如题目的线程顺序执行,还有一个线程等待多个线程该如何实现,一个系统最多只能100个人登录如何实现?

就是想看看你是否了解Condition、CountDownLatch、CyclicBarrier、Semaphore的用法。

Condition只能由Lock锁创建,一般适用于两个线程之间的等待。

CountDownLatch是AQS共享模式的实现,state状态值表示计数器,await方法会使线程进入阻塞状态直到其他线程调用countdown方法将计数器减为0,CountDownLatch适用于一对多等待的场景。

CyclicBarrier常用来与CountDownLatch作比较,它也可以实现CountDownLatch一对多的等待,CyclicBarrier是一个栅栏,初始化指定屏障个数,所有的线程都到达屏障之后才可以继续执行,会使线程阻塞,且其可以重复使用。

Semaphore是许可证,常用来限制访问的数量,也是AQS共享模式的实现,state状态值用来表示许可证的数量。

好了,今天的题目分析就到这里了。

大家不要为了面试去记忆题目,一定是自己理解了之后去总结语言。更不要为了面试去学习,所有的学习最终在实战中用到才是目的。

当你一点点的进步的时候,你会发现知识体系的广度和深度会对你的代码质量提高很多,遇到问题再也不怕了,就算是拧螺丝也是一颗好螺丝。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值