复盘女朋友面试4个月的并发面试题

简介: 该文章主要复盘了关于并发的面试题,包括线程池的使用场景、原理、参数合理化设置,以及ThreadLocal、volatile、synchronized关键字的使用场景和原理,还介绍了juc并发工具包中aqs的原理,强调在面试中要将自己理解的点与面试官讲透。

又来了,第三波女朋友面试4个月的面试题复盘,今天是关于并发的题目。

直接上题目。并发题目相对还是比较少的,还是老套路,高频的面试题已经标星,有面试需要的同学可以先点收藏起来。 image.png

并发问的问题不是特别多,但是并发的知识其实是比较难懂,也很难讲清楚。这些面试题中问的最多的问题就是线程池,其实线程池的原理和源码是相对比较容易理解的。

image.png

下面开始进入面试题的复盘。

线程池面试题

线程池的使用场景

线程池是一种池化复用线程的设计思想,帮助解决应用无限创建线程和销毁线程开销的问题。

在项目中,我们通常有几类场景会使用线程池来优化我们的程序性能:

  • 1、多任务并行执行场景:针对C端请求的接口,如果接口涉及到多个可以并行执行的rpc或者db等io耗时的操作,我们会使用线程池来优化,加快接口响应速度。

下面的例子就是,比如在用户权益页面,使用线程池并行查询查询优惠券和积分等权益。


ExecutorService threadPool = Executors.newFixedThreadPool(5);

CompletableFuture<Void> userTask = CompletableFuture.runAsync(()->{
   
   

//查询用户信息

userService.getUser();

}, threadPool);

CompletableFuture<Void> couponTask = CompletableFuture.runAsync(()->{
   
   

//查询用户优惠券

couponService.getUserCoupon();

}, threadPool);

CompletableFuture<Void> scoreTask = CompletableFuture.runAsync(()->{
   
   

//查询用户优惠券

scoreService.getUserScore();

}, threadPool);

CompletableFuture<Void> allTask = CompletableFuture.allOf(userTask, couponTask, scoreTask);

allTask.get(1000, TimeUnit.MILLISECONDS);
  • 2、批量异步处理任务场景:针对一些定时跑批量任务的后台job,我们可以通过线程池来提升处理任务的吞吐量。

比如给会员用户发送过期提醒的短信或者push通知。我们一般会分页查询到需要发过期提醒的会员用户,然后分批发送push通知。


List<List<String>> userIdGroup = new ArrayList<>();

for (List<String> userIdList : userIdGroup) {
   
   

//push

threadPool.execute(() -> {
   
   

//发送过期提醒

doPush(userIdList);

});

}
  • 3、中间件的使用

在中间件的源码中,有很多的线程池的使用案例:

Tomcat就是使用线程池来处理客户端请求。

dubbo通过线程池处理rpc的请求。

rocketmq的消费者通过线程池来处理业务逻辑。

谈谈线程池的原理

线程池的工作原理,我们需要了解它的实现,我们可以从提交任务和执行任务来给面试官说明。

提交任务是execute和sumbit方法的流程:

image.png

执行任务主要是java.util.concurrent.ThreadPoolExecutor#runWorker方法的流程:

通过不断的从阻塞队列获取任务,调用task.run();方法执行任务。

什么时候回收线程:

  • 线程数超过了最大线程数或者指定时间都没有获取到任务

  • 核心线程数大于1以及任务队列是空的


boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

if ((wc > maximumPoolSize || (timed && timedOut))

&& (wc > 1 || workQueue.isEmpty())) {
   
   

if (compareAndDecrementWorkerCount(c))

return null;

continue;

}

try {
   
   

Runnable r = timed ?

workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :

workQueue.take();

if (r != null)

return r;

timedOut = true;

} catch (InterruptedException retry) {
   
   

timedOut = false;

}

线程池参数如何合理化设置

这个问题,我们需要根据实际的业务场景。

  • 如果是io密集型

核心线程池设置成 2 * cpu

  • 如果是cpu密集型

可以把核心线程数设置为核心数+1。

为什么要加一呢?

《Java并发编程实战》一书中给出的原因是:即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费。

上面都是理论上的设置,最后我们需要和面试官说明,我们可以将线程池的参数进行动态配置化,根据压测情况和实际线上的流量进行动态调整。

线程同步工具面试题

ThreadLocal的使用场景和原理

threadLocal主要就是用于隐式传参

ThreadLocal其实可以理解成一个工具类,它负责操作Thread对象的ThreadLocalMap属性的,更新和查询指定线程的参数。

我们可以和面试官深入谈一下对ThreadThreadLocalMapThreadLocal三者的关系,那么面试官就可以明白了你对ThreadLocal真正的理解了。

1、Thread持有ThreadLocalMap的引用,他们是1对1关系。

2、Entry是ThreadLocalMap的内部类,并且ThreadLocalMap持有Entry类型的数组。也就是一个ThreadLocalMap对应多个Entry。

3、ThreadLocal和ThreadLocalMap的关系是最难描述的,因为

ThreadLocalMap是ThreadLocal的子类,而ThreadLocalMap中存储的key类型是ThreadLocal。并且ThreadLocal是弱类型的。

借用网上的一张图:

image.png

volatile关键字的作用和原理

volatile关键字是解决并发问题的轻量级手段,它能够解决可见性和有序性。

为什么有并发问题呢?其实是因为Java内存模型决定的,Java中每个线程都有工作内存的概念,如果多个cpu都同时操作同一个共享变量可能就会出现问题了。

volatile在Jvm层面使用的内存屏障技术来解决并发问题的。

针对volatile关键字的读操作:

基于c++的volatile关键字,每次从主存中读取。

C++的volatile禁止对这个变量相关的代码进行乱序优化(重排序),也就具有内存屏障的作用了。

针对volatile关键字的写操作:

基于c++的volatile关键字和 lock addl指令的内存屏障,每次将新值刷新到主存,同时其他cpu缓存的值失效。

synchronized关键字的使用场景和原理

synchronized可以实现多线程同步等待,将多线程并行执行改成串形执行,解决多线程并发安全问题。

它能够解决可见性,有序性,原子性。

原理层面,我们可以从jdk1.6的升级前后来说明:

在JDK1.6之前,synchronized属于重量级锁,效率低下,因为Monitor是依赖于底层的操作系统的互斥原语mutex来实现,这会导致线程在“用户态和内核态”两个态之间来回切换,对性能有较大影响。

  庆幸的是在JDK1.6之后Java官方对从JVM层面对synchronized较大优化,所以现在的synchronized锁效率也优化得很不错了,JDK1.6之后,为了减少获得锁和释放锁所带来的性能消耗,为了减少这种重量级锁的使用,引入了轻量级锁和偏向锁,这两个锁可以不依赖Monitor的操作。

偏向锁是发生只有一个线程抢占锁的阶段,只需要通过cas设置线程id就可以完成加锁.

轻量级锁是通过自适应自旋锁来实现的,在自旋一定次数如果加锁成功,就是轻量级锁。

如果轻量级锁加锁没有成功,则会转换成重量级锁。重量级锁加锁成功后执行内存屏障,保证可见性和有序性,其他未加锁成功的线程会进入等待队列同时进入阻塞队列。

juc并发工具包

讲一讲aps的原理

aqs(AbstractQueuedSynchronizer)是一个实现线程同步的底层工具,它是提供两种功能:

  • 独占锁,同时一个线程获取锁

  • 共享锁,同时多个线程获取锁

可以和面试官说,aqs定义了实现独占锁和共享锁的接口,如果需要实现自定义的锁,只需要继承aqs并实现他的的方法就可以。

aqs内部由一个fifo双向链表和一个整形状态字段来实现控制加解锁和加锁线程排队的能力。

哈哈,并发专题很难讲清楚,面试的时候和面试官贵在把自己理解的点和面试官讲透。

image.png

相关文章
|
4月前
|
并行计算 数据挖掘 大数据
[go 面试] 并行与并发的区别及应用场景解析
[go 面试] 并行与并发的区别及应用场景解析
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
2月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
45 0
|
4月前
|
存储 缓存 网络协议
复盘女朋友面试4个月的Java基础题
这篇文章是关于Java基础面试题的复盘,涵盖了HashMap原理、对象序列化作用等高频面试问题,并强调了Java基础知识的重要性。
复盘女朋友面试4个月的Java基础题
|
4月前
|
SQL 存储 关系型数据库
复盘女朋友面试4个月的Mysql面试题(1万字)
该文章详细分析了Ribbon的超时配置是否会覆盖OpenFeign的超时配置,并探讨了OpenFeign超时配置能否动态实时修改生效的问题。
复盘女朋友面试4个月的Mysql面试题(1万字)
|
4月前
|
Java 程序员 调度
面试准备-并发
面试准备-并发
|
4月前
|
消息中间件 缓存 负载均衡
复盘女朋友面试4个月的RocketMQ面试题
这篇文章复盘了面试中关于RocketMQ的高频题目,包括架构组成、使用姿势、功能原理及高级特性,并强调了理解这些实现机制对于面试成功的重要性。
复盘女朋友面试4个月的RocketMQ面试题
|
4月前
|
存储 缓存 Java
复盘女朋友面试4个月的Spring面试题
该文章复盘了关于 Spring 的面试题,包括 Spring 的好处、Bean 的生命周期、Spring 循环依赖的解决方法、AOP 的原理以及 Spring Boot 自动装配的原理等,强调对 Spring 核心原理的清晰理解对于回答面试题的重要性。
复盘女朋友面试4个月的Spring面试题
|
4月前
|
安全 Go 调度
[go 面试] 深入理解并发控制:掌握锁的精髓
[go 面试] 深入理解并发控制:掌握锁的精髓
|
4月前
|
JavaScript 前端开发 Java
面试官:假如有几十个请求,如何去控制并发?
面试官:假如有几十个请求,如何去控制并发?