Java岗大厂面试百日冲刺【Day45】— 实战那些事儿
本文已获得原作者 _陈哈哈 授权并经过重新整理规划后发布。
本栏目Java开发岗高频面试题主要出自以下各技术栈:Java基础知识、集合容器、并发编程、JVM、Spring全家桶、MyBatis等ORMapping框架、MySQL数据库、Redis缓存、RabbitMQ消息队列、Linux操作技巧等。
面试题1:什么是线程阻塞?什么情况会导致线程阻塞
当线程A在运行一段代码的时,这时候另一个线程B也需要运行,但是在运行过程中的线程A执行完成之前,另一个线程B是无法获取到执行对象锁的,这个时候就会造成线程阻塞
。
sleep()、wait()
调用后都会暂停当前线程并让出cpu的执行时间,但不同的是sleep不会释放当前持有的对象的锁资源,到时间后会继续执行,而wait会放弃所有锁并需要notify/notifyAll后重新获取到对象锁资源后才能继续执行。
java程序中出现线程阻塞的几种情况:
1、睡眠状态:
Thread.sleep (long millis)
方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态
。sleep()平台移植性好。
2、等待状态:
当一个线程正在运行时调用了wait()
方法,此时该线程需要交出CPU执行权,也就是将锁释放出去,交给另一个线程,该线程进入等待状态,但与睡眠状态不一样的是,进入等待状态的线程不需要设置睡眠时间
,但是需要执行notify()或者notifyall()
来对其唤醒
,自己是不会主动醒来的,等被唤醒之后,该线程也会进入就绪状态,但是进入仅需状态的该线程手里是没有执行权的,也就是没有锁,而睡眠状态的线程一旦苏醒,进入就绪状态时是自己还拿着锁的。
3、礼让状态:
Thread.yield()
方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程
。yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。
4、自闭状态:
当一个线程正在运行时,调用了一个join()
方法,此时该线程会进入阻塞状态,另一个线程会运行,直到运行结束后,原线程才会进入就绪状态
。这个比较像是”走后门“,本来该先把你的事情解决完了再解决后边的人的事情,但是这时候有走后门的人,那就会停止给你解决,而优先把走后门的人事情解决了;
5、suspend() 和 resume() :
两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。
典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。Thread中suspend()和resume()两个方法在JDK1.5中已经废除
,因为有死锁倾向。
追问1:线程阻塞会导致进程阻塞么?
回答这个问题,我们得了解一下线程模型(下述对应关系为 线程
对内核调度实体
)
- 多对1用户级线程模型
- 1对1内核级线程模型
- 多对多两级线程模型
多对1用户级线程模型
- 线程的创建、调度、同步,由所属进程的用户空间线程库实现。
- 用户态线程,对内核几乎是透明的(许多操作不需要内核接管)
- 但线程总要有一些操作经过内核,比如系统调用。
- 不需要频繁的内核态/用户态切换,处理速度非常快。
- 该模式下,当进程的某个线程,系统调用(比如I/O)阻塞时,该进程也会阻塞。
该模式下,进程的所有线程,都对应一个内核调度实体(KES),多对一,并且内核不知道这个进程有哪些线程。KES无法将其他线程,调度到其他处理器上。该进程(所有的线程)被阻塞,直到本次系统调用(比如I/O)结束。
1对1内核级线程模型
- 每个用户线程都对应一个的内核调度实体。
- 内核会对每个线程进行调度,可以调度到其他处理器上。
- 线程每次操作会在用户态和内核态切换。
- 线程数量过多时,对系统性能有影响。
多对多两级线程模型
- 每个用户线程拥有多个内核调度实体
- 多个用户线程也可以对应一个内核调度实体
- 实现该模型非常复杂。
线程阻塞时,在多对1用户级线程模型下,会导致所属进程阻塞
。
在1对1或多对多模型下,不会导致进程阻塞
,目前linux基本上都采用一对一模型。
面试题2:怎么理解阻塞和非阻塞
阻塞和非阻塞关注的是程序在等待
调用结果
(消息,返回值)时的状态
。
阻塞调用
:指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用
:指在不能立刻得到结果之前,该调用不会阻塞当前线程。
就像领导周末打电话问我能不能去公司加班,我支支吾吾半天领导没听懂我说的啥,如果是阻塞式调用,领导(线程)会把自己挂起,一直追问我直到我告诉他能不能去。
而如果是非阻塞式调用,领导就给我发了条语音,然后让我尽快回复他,他就可以摸鱼或干别的去了,但老板会时不时看下手机有没有回复(定时轮询)。
面试题3:怎么理解并发和并行
- 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
- 你吃饭吃到一半,电话来了,你停了下来接了电话,然后一手筷子,一手电话,说一句话,咽一口饭。这说明你支持并发。
- 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,咽一口饭同时说一句话,这说明你支持并行。但这光靠一张嘴是办不到的,至少两张嘴!
并发的关键是:你有处理多个任务的能力,不一定要同时。
并行的关键是:你有同时处理多个任务的能力。
所以我认为它们最关键的点就是:是否同时
。