Java坑人面试题系列: 线程/线程池(高级难度)
ExecutorService
接口及相关API细节详解。
Java Magazine上面有一个专门坑人的面试题系列: https://blogs.oracle.com/javamagazine/quiz-2。
这些问题的设计宗旨,主要是测试面试者对Java语言的了解程度,而不是为了用弯弯绕绕的手段把面试者搞蒙。
如果你看过往期的问题,就会发现每一个都不简单。
这些试题模拟了认证考试中的一些难题。 而 “中级(intermediate)” 和 “高级(advanced)” 指的是试题难度,而不是说这些知识本身很深。 一般来说,“高级”问题会稍微难一点。
问题(高级难度)
此问题的目的是考察如何通过 Runnable
和 Callable
来创建任务,并使用 ExecutorService
来并发执行。
我们有一个 Logger
类,定义如下所示:
class Logger implements Runnable {String msg;public Logger(String msg) {this.msg = msg;}public void run() {System.out.print(msg);}
}
并给出如下使用的代码:
Stream<Logger> s = Stream.of(new Logger("Error "),new Logger("Warning "),new Logger("Debug "));
ExecutorService es =Executors.newCachedThreadPool();
s.sequential().forEach(l -> es.execute(l));
es.shutdown();
es.awaitTermination(10, TimeUnit.SECONDS);
这里省略了相关的 import
语句, 假设代码能编译并正常启动。 请选择两项可能的输出结果:
- A、 Error Debug Warning
- B、 Error Warning Debug
- C、 Error Error Debug
- D、 Error Debug
答案和解析
这道试题属于 Executors
类和 ExecutorService
接口相关的考点,顺带考察 Executors
工具类自带的 ExecutorService
线程池实现。
在Java的早期版本中,需要程序员手工创建和管理线程。线程是系统内核级的重要资源,并不能无限创建; 而且创建线程的开销很大,所以开发中一般会使用资源池模式,也就是创建 “线程池”。通过线程池,可以用少量的线程,来执行大量的任务。
线程池的思路是这样的: 与其为每个任务创建一个线程,执行完就销毁; 倒不如统一创建少量的线程, 然后将任务逻辑用 Runnable
包装起来, 提交给线程池来调度执行。
有任务需要调度的时候,线程池找一个空闲的线程,并通知他干活。 任务执行完成后,再将这个线程放回池子里,等待下一次调度。
Java 5.0 开始提供标准的线程池API。 通过 Executor
和 ExecutorService
接口定义了线程池以及支持的交互操作。
另外,我们可以使用 Executors
的静态工厂方法来实例化 ExecutorService
的各种实现。
相关的基础类和接口都位于 java.util.concurrent
包中, 在编写简单的并发任务时,可以直接使用。
Executor
是顶层接口, 定义了执行 Runnable
任务的方法;但我们一般用的是子接口 ExecutorService
及其实现。
ExecutorService
接口中增加了处理 Callable
的方法, 以及关闭线程池的功能。
实现 Callable
接口的任务会返回一个结果, 调用方可以通过提交任务时返回的 Future
对象,来异步获取任务的执行状态和结果,这样就对任务有了一定的管理和控制能力。
Executor
和 ExecutorService
接口并没有规定使用哪种调度策略来执行。
- 有些线程池,使用固定数量的线程来并发地执行任务,新提交的任务要等到有空闲线程才会被执行。
- 有的线程池, 在工作负载上升时自动增加线程,并在需求降低时清理掉一部分线程。
- 还有的线程池只使用单个线程,直接按顺序执行提交的任务。
这些特征取决于具体的实现,需要开发者根据业务系统的特征来权衡,并选择适当的线程池。 针对这几类线程池,Executors
工具类提供了三种工厂方法:
newFixedThreadPool
newCachedThreadPool
newSingleThreadExecutor
前两个方法创建的线程池可以有多个worker线程, 而 newSingleThreadExecutor
方法创建的线程池则只有单个线程。
回到前面的问题, 试题中给出的代码创建了缓存模式的线程池。
这类线程池会根据需要生成新的worker线程,并清理一段时间内没有使用到的线程。
但缓存模式的线程池有一个严重缺点: 创建的线程数有可能不被限制, 那样的话会导致大量的资源占用。 在高负载场景下,可能会由于资源争用而导致性能急剧下降。
因为创建的线程池具有多个线程, 所以后面提交的任务可以并发执行。
无论谁先开始,我们都无法对其执行进度做出精确预测。
也就是说,他们输出消息的顺序可能是任意的。
由此得知, 选项A
和 选项B
都 正确
。
ExecutorService
会保证提交的任务最多被执行一次。
在某些情况下,任务可能不会执行,或者在执行完成之前线程池就被关闭了。
因为具有最多执行一次的特征,所以我们不会看到任何重复的消息。因此可以判断,选项C不正确
。
在调用 shutdown
方法之后,ExecutorService
会拒绝新的任务提交请求, 但已有的任务会继续运行,直到所有的作业全部执行完才会关闭。
因此在这里给的代码中, 三个消息都会看到。 因此可知,选项D不正确
。
顺便提一句,可能有些读者会认为,如果在10秒内执行不完, 那么选项D也可能是正确的。
但反过来说,如何确定这个消息会被打印呢?
因为试题中给出的任务逻辑非常简单,很明显不可能10秒钟还执行不完。
而且我们通过分析能判断出 选项A
和 选项B
是正确的, 那么做题时就可以将这种不可能的情况排除。
当然,你可能对选项D感兴趣,因为在其他某些极端的情况下, 作业无法在10秒内完成,比如恰好在这个时刻操作系统启动升级或更新。
请注意,在给定的代码中,没有任何证据表明 JVM 将被强行关闭。
而且默认创建的线程都是 非守护线程
(nondaemon thread),因此,在作业完成之前,JVM 不会退出。
所以,如果允许程序运行,则对应的消息都会被打印出来。
总结
正确的选项是 A
和 B
。
相关链接
- Java坑人面试题系列: 包装类(中级难度)
- Java坑人面试题系列: 比对while与for循环(中级)
- Java坑人面试题系列: 集合(高级)
- Java坑人面试题系列: 线程/线程池(高级)
- Java坑人面试题系列: 变量声明(中级)
原文链接: https://blogs.oracle.com/javamagazine/quiz-advanced-executor-service
Java坑人面试题系列: 线程/线程池(高级难度)相关推荐
- Java坑人面试题系列: 比对while与for循环(中级难度)
Java Magazine上面有一个专门坑人的面试题系列: https://blogs.oracle.com/javamagazine/quiz-2. 这些问题的设计宗旨,主要是测试面试者对Java语 ...
- 【Java实习生面试题系列】-- 多线程篇四
文章目录 1. 说下对同步器 AQS 的理解? 2. AQS 的原理是什么? 3. AQS 底层使用了模板方法模式,你能说出几个需要重写的方法吗? 4. 说下对 Semaphore.CountDown ...
- 【Java实习生面试题系列】-- JVM篇一
文章目录 1. 说一下 JVM 的主要组成部分?及其作用? 2. 堆和栈的区别是什么? 3. 对象的访问定位的两种方式? 4. 判断垃圾可以回收的方法有哪些? 5. 被标记为垃圾的对象一定会被回收吗? ...
- Java经典面试题:一个线程两次调用start()方法会出现什么情况?
大家好,我是 Oracle首席工程师杨晓峰. 今天想和大家深入聊聊线程,相信大家对于线程这个概念都不陌生,它是Java并发的基础元素,理解.操纵.诊断线程是Java工程师的必修课,但是你真的掌握线程了 ...
- java 批量插入clob_SpringBoot系列(16)线程池Executors并发编程之批量查询-插入数据
在上篇文章中Debug给大家分享介绍了"Java线程池-多线程的其中一种应用场景~广播式给所有有效用户发送邮件(通知)",本篇文章我们将继续向前迈进,继续介绍并实战"线程 ...
- Java多线程面试题:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次, 接着再回到主线程又循环100,如此循环50次
近在学习多线程,特贴在这里,并附上自己所作答案(参考与张孝祥老师的方法). 题目如下: 子线程循环10次,接着主线程循环100,接着又回到子线程循环10次, 接着再回到主线程又循环100,如此循环50 ...
- Java基础面试题系列
本文收集了一些经典的Java面试题 1.面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面.抽象只关 ...
- 【Java实习生面试题系列】-- 多线程篇一
文章目录 1. 并行和并发有什么区别? 2. 线程和进程的区别? 3. 守护线程是什么? 4. 创建线程的几种方式? 5. Runnable 和 Callable 有什么区别? 6. 线程状态及转换? ...
- 一文整理总结常见Java后端面试题系列——Java虚拟机篇(2022最新版)
关于作者
最新文章
- Ubuntu 系统如何修改主机名
- 使用特异性特征提取网络辅助分类任务
- java mvc httpget怎么使用_springMVC正确使用GET POST PUT和DELETE方法,如何传递参数
- java redis 批量删除key_Redis,就是这么朴实无华
- 查询优化器内核剖析第一篇
- css float(脱离正常流:向左/右浮动直到父元素/另一float, 不占空间)
- tomcat 启动异常 The web application [ROOT] registered the JDBC driver [xxx] but failed to unregister it
- Nanachi发布:基于 React 的多端小程序转译框架
- git 初始化git存储库_什么不保存到Git存储库中
- 先虚拟主机后云服务器,先虚拟主机后云服务器
- 详解RecyclerView下拉刷新与上拉更多
- 定时器精度对性能的影响_Comet CAA-500天线分析仪 | 高精度模拟十字针同时显示SWR和阻抗...
- sql ddl中key_SQL DDL:SQL Server中SQL DDL命令入门
- CSS——简写属性(在padding和margin这样的简写属性中,值赋值的顺序是top、right、bottom、left)...
- 中国游戏的未来在哪里 - 游戏行业20年历史观察及趋势分析
- 【模板一】计算机XXXX系统-毕业设计
- WebSockt面试题
- 用python计算直角三角形斜边长
- 迅睿cms模板,迅睿cms模板建站,迅睿模板主题开发
- 树莓派第一次使用WIN10电脑远程连接(无显示器)