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

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

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

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

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

线程池面试题

线程池的使用场景

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

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

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

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

java 复制代码
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通知。

java 复制代码
List<List<String>> userIdGroup = new ArrayList<>();

for (List<String> userIdList : userIdGroup) {

//push

threadPool.execute(() -> {

//发送过期提醒

doPush(userIdList);

});

}
  • 3、中间件的使用

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

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

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

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

谈谈线程池的原理

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

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

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

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

什么时候回收线程:

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

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

java 复制代码
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是弱类型的。

借用网上找的一张图:

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双向链表和一个整形状态字段来实现控制加解锁和加锁线程排队的能力。

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

相关推荐
测试199843 分钟前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试
AskHarries1 小时前
Java字节码增强库ByteBuddy
java·后端
佳佳_1 小时前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
马剑威(威哥爱编程)1 小时前
MongoDB面试专题33道解析
数据库·mongodb·面试
许野平2 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
BiteCode_咬一口代码3 小时前
信息泄露!默认密码的危害,记一次网络安全研究
后端
独行soc4 小时前
#渗透测试#SRC漏洞挖掘#深入挖掘XSS漏洞02之测试流程
web安全·面试·渗透测试·xss·漏洞挖掘·1024程序员节
理想不理想v4 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
齐 飞4 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod4 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发