上海某小公司面试题:Java线程池来聊聊

Java字符串池的高频测试点直接给出了结论。

基本概念: 线程池通过重用线程来减少创建和销毁的开销。
控制线程数量,防止资源耗尽。

主要成分:
线程池管理器:创建和销毁线程池,并配置参数。

工作线程:一组执行任务的线程。

任务队列:阻塞队列,临时存放要执行的任务。

任务拒绝策略:队列满时的处理策略。

实现原理: ThreadPoolExecutor类实现了线程池。
构造函数参数:
corePoolSize:核心线程数。

maximumPoolSize:最大线程数。

keepAliveTime:空闲线程的生存时间。

工作队列:任务队列。

threadFactory:线程工厂。

治疗师:拒绝策略。

工作流程: 1 . 提交任务:execute()或submit()。
2 .定义线程复用:
如果当前线程数<主线程数,则创建一个新线程。

否则,任务将被存储在队列中。
3 .队列处理:
队列未满,任务已添加到队列中。

队列已满且线程数<最大线程数,创建新线程。
4 .拒绝策略:当线程数达到最大且队列已满时激活。

主要参数:
corePoolSize:空闲线程的最小数量,不会被回收(除非设置了allowCoreThreadTimeOut)。

maximumPoolSize:最大线程数,暂时超过最大核心线程数。

任务队列:
无界队列(LinkedBlockingQueue):可能导致OOM。

有界队列(ArrayBlockingQueue):需要配合拒绝策略。

SynchronousQueue:不直接存储和传输任务。

拒绝政策:
AbortPolicy:默认情况下,抛出RejectedExecutionException。

CallerRunsPolicy:提交线程的任务执行情况。

DiscardPolicy:直接丢弃任务。

DiscardOldestPolicy:丢弃队列中最旧的任务。

主题池类型: JDK提供了四种预定义的线程池:
FixedThreadPool:固定大小的线程池。

CachedThreadPool:缓存线程池。

SingleThreadExecutor:单线程池。

ScheduledThreadPool:计划任务线程池。
注:在生产环境中,建议使用直接使用 ThreadPoolExecutor 构造函数,避免使用 Executors 工厂方法。

监控指标: 通过ThreadPoolExecutor提供的方法获取运行状态:
getCorePoolSize():核心线程数。

getActiveCount():当前活动主题的数量。

getCompletedTaskCount():已完成的任务数。

getQueue().size():任务队列中的任务数量。

最佳实践: 参数配置原则:
CPU密集型任务:线程数≈CPU核心数。

I/O密集型任务:线程数≈CPU核心数(1 +平均等待时间/平均计算时间)。
异常处理: 必须在任务内捕获异常,否则线程可能会终止。
优雅的结束:
shutdown():不再接受新任务。

awaitTermination():等待现有任务完成(强制关闭超时)。

常见面试问题:
线程池如何实现线程复用? 通过重写 Thread.run() 方法,循环在任务执行后获取新任务。

为什么不建议使用执行器创建线程池? FixThreadPool和SingleThreadExecutor使用无限队列,这可能会导致OOM。
CachedThreadPool 允许创建无限数量的线程,这会耗尽系统资源。

如何选择任务队列类型? 高吞吐量场景:使用无限队列(必须监控内存使用情况)。
实时性要求高:使用同步传输队列(如SynchronousQueue)。
平衡场景:使用有限的队列(必须适当调整大小)。

深入理解线程池需要分析源码,尤其是ThreadPoolExecutor的addWorker()方法以及Worker类的实现逻辑。
建议使用调试工具跟踪线程创建、任务执行、线程回收的整个过程。

如何在Java中使用CompletableFuture结合线程池

结论:在Java中使用CompletableFuture结合自定义线程池的关键是隔离资源、精确控制线程、灵活的任务链。

步骤: 1 .创建自定义线程池:核心4 ,最大8 ,生存6 0秒,队列1 00,自定义线程名称,AbortPolicy拒绝策略。
2 、使用线程池执行异步任务:supplyAsync、thenApplyAsync等,指定线程池。
3 、链式操作时切换线程池:为IO密集型任务和CPU密集型任务分配不同的线程池。
注意事项:
关闭线程池:使用shutdown()或shutdownNow()。

避免长期僵局:设定一个时间限制。

合理配置文件集的大小:IO密集型≈并发数,CPU密集型≈核心数。

小心使用无界队列:它们可能会导致内存溢出。

示例: 周 公共类 CompletableFutureWithThreadPool { 公共静态无效主(字符串[] args){ ExecutorService threadPool = new ThreadPoolExecutor(4 , 8 , 6 0L, TimeUnit.SECONDS, new LinkedBlockingQueue(1 00), r -> { 线程t=线程(r); t.setName("异步线程-" + t.getId()); 返回t; });
CompletableFuture future = CompletableFuture.supplyAsync(() -> { System.out.println("任务开始于:" + Thread.currentThread().getName()); 尝试{ 线程睡眠(1 000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } 返回“异步结果”; }, threadPool).thenApplyAsync(结果 -> { System.out.println("正在处理:" + Thread.currentThread().getName()); 返回结果.toUpperCase(); }, threadPool).thenAccept(finalResult -> System.out.println("最终: " + FinalResult)).exception (ex -> { System.err.println("错误:" + ex.getMessage()); 返回空值; });
threadPool.shutdown(); }
总结:通过定制线程池,可以隔离资源,精准控制线程,灵活切换任务链,显着提升异步代码的性能和可靠性。

Java线程池优化实战:如何合理设置核心与最大线程数

这里有一个陷阱:直接使用默认的 AbortPolicy 拒绝策略。

不信:所有作品都使用相同的锡线。

不要这样做:不要根据任务类型和系统能力调整线程池大小和队列类型。

JAVA-快速了解线程池的基本原理

说实话,当我谈到线程池时,我特别喜欢思考我们加入连接高并发接口时遇到的陷阱。
当时我负责后端的老板强迫我使用newFixedThreadPool来进行闪购活动。
结果队列爆炸了,系统立马就挂了。
那么今天我们就不废话了,根据实际场景来拆解一下。

我们先来说说最关键的东西——队列。
记得在一次压力测试时,盯着屏幕发现,当任务队列积压的任务达到2 000个时,新的请求就开始报RejectedExecutionException。
我当时就想,队列容量只有1 00人吗?检查源码后发现LinkedBlockingQueue的默认值是Integer.MAX_VALUE。
如果真的用这个的话,内存会直接爆炸。
所以实际使用时,应该自己创建一个新的有界队列,比如ArrayBlockingQueue(1 00),而不是真正使用无界队列。
我被困在这一点上。
我记得数据是Integer.MAX_VALUE,但我建议你检查源代码。

我们来谈谈线程数。
有一次接一个爬虫项目,老板坚持用newCachedThreadPool,说工作量波动很大,这样最省钱。
结果呢?监控显示最大线程数已经升至5 00+,CPU正在烧干。
后来换成了newFixedThreadPool(1 0, 2 0),定时回收非核心线程,系统立刻就稳定了。
说白了,对于CPU密集型任务,永远不要使用无限制的线程池,严格控制核心线程数。
当时我不明白老板为什么一定要这么做。
他可能认为拥有更多线程会让事情进展得更快。
事实上,他不知道线程交换本身也是有开销的。

拒绝策略也很有趣。
一旦我们连接到第三方 API。
对方的接口限制很大,而我们的业务端却总是超时。
后来我改用CallerRunsPolicy,超时任务由提交任务的线程运行。
虽然增加了主线程的负载,但至少API没有报告任何错误。
然而,这种策略必须谨慎使用。
我的一个同事使用了它,生产环境的CPU上升到9 0%。
我花了很长时间才弄清楚,原来预定的作业提交逻辑有问题,作业量突然暴涨。

最后,一些实用的技巧。
NewScheduledThreadPool特别适合计划任务。
我曾经做过一个日志限制任务,我用这个来执行,延迟5 分钟,周期1 小时。
它比在主线程中使用 Thread.sleep 可靠得多。
而且它的好处是不需要每次提交的时候都创建一个新的ScheduledExecutorService。
系统会给你默认的SingleThreadExecutor,省去了你的麻烦。

其实线程池用得好的话可以省去很多麻烦,但是用得不好的话……就像我那个被老板强制使用FixedThreadPool的界面,上线第一天我就跪了。
所以不要只看理论,一定要结合实际场景。
比如高并发的秒杀,不要使用有界队列,否则用户请求会直接被阻塞;但是对于后台同步任务,您需要使用有界队列,否则谁负责内存溢出?