BAT大厂面试必问专题之Java多线程
根据众多面试的同学反馈的面试题,给大家整理一版最新的面试专题,希望对大家有所帮助。
Java多线程
1.先看问题
先来看看经常会问到的问题。
- 线程池的原理,为什么要创建线程池?创建线程池的方式;
- 线程的生命周期,什么时候会出现僵死进程;
- 说说线程安全问题,什么实现线程安全,如何实现线程安全;
- 创建线程池有哪几个核心参数? 如何合理配置线程池的大小?
- volatile、ThreadLocal的使用场景和原理;
- ThreadLocal什么时候会出现OOM的情况?为什么?
- synchronized、volatile区别、synchronized锁粒度、模拟死锁场景、原子性与可见性;
看看你能回答几个哦。
然后我们再来看看每个问题我们应该怎么来回答吧!
2.再看答案
2.1 Q1:线程池的原理
线程池的原理,为什么要创建线程池?创建线程池的方式有哪些?
原理和创建线程池的实现请我之前整理的这篇文章:
Java线程池原理讲解
创建线程池的几种方式:
- ThreadPoolExecutor
- ThreadScheduledExecutor
- ForkJoinPool
2.1 Q2:线程的生命周期
线程的生命周期,什么时候会出现僵死进程;
僵死进程是指子进程退出时,父进程并未对其发出的SIGCHLD信号进行适当处理,导致子 进程停留在僵死状态等待其父进程为其收尸,这个状态下的子进程就是僵死进程。
2.3 Q3:线程安全问题
说说线程安全问题,什么是线程安全,如何实现线程安全;
- 线程安全 - 如果线程执行过程中不会产生共享资源的冲突,则线程安全。
- 线程不安全 - 如果有多个线程同时在操作主内存中的变量,则线程不安全
实现线程安全的三种方式
1)互斥同步
- 临界区:syncronized、ReentrantLock
- 信号量 semaphore
- 互斥量 mutex
2)非阻塞同步
- CAS(Compare And Swap)
3)无同步方案
- 可重入代码
- 使用Threadlocal 类来包装共享变量,做到每个线程有自己的copy
- 线程本地存储
多线程的安全机制:数据安全机制
2.4 Q4:线程池参数问题
创建线程池有哪几个核心参数? 如何合理配置线程池的大小?
1.核心参数
首先我们来看下核心参数:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数量大小
int maximumPoolSize, // 线程池最大容纳线程数
long keepAliveTime, // 线程空闲后的存活时长
TimeUnit unit,
//缓存异步任务的队列 //用来构造线程池里的worker线程
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
//线程池任务满载后采取的任务拒绝策略
RejectedExecutionHandler handler)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.参数说明
相关参数介绍:
- 当线程池中线程数量小于 corePoolSize 则创建线程,并处理请求。
- 当线程池中线程数量大于等于 corePoolSize 时,则把请求放入 workQueue 中,随着线程池 中的核心线程们不断执行任务,只要线程池中有空闲的核心线程,线程池就从 workQueue 中取 任务并处理。
- 当 workQueue 已存满,放不下新任务时则新建非核心线程入池,并处理请求直到线程数目 达到 maximumPoolSize(最大线程数量设置值)。
- 如果线程池中线程数大于 maximumPoolSize 则使用 RejectedExecutionHandler 来进行任 务拒绝处理。
具体可以参考本文的详解:线程池核心配置讲解
3.线程池大小
然后我们需要来看先线程池的大小分配了。
线程池究竟设置多大要看你的线程池执行的什么任务了,CPU密集型、IO密集型、混合型,任 务类型不同,设置的方式也不一样。
任务一般分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。
3.1 CPU密集型
尽量使用较小的线程池,一般Cpu核心数+1
3.2 IO密集型
-
方法一:可以使用较大的线程池,一般CPU核心数 * 2 IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间
-
方法二:(线程等待时间与线程CPU时间之比 + 1)* CPU数目
下面举个例子:
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
3.3 混合型
可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定
2.5 Q5:volatile和ThreadLocal
问题:volatile、ThreadLocal的使用场景和原理?
volatile原理
volatile变量进行写操作时,JVM 会向处理器发送一条 Lock 前缀的指令,将这个变量所在缓 存行的数据写会到系统内存。
Lock 前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其 后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内 存屏障这句指令时,在它前面的操作已经全部完成。
volatile的适用场景:
- 状态标志,如:初始化或请求停机
- 一次性安全发布,如:单列模式
- 独立观察,如:定期更新某个值
- “volatile bean” 模式
- 开销较低的“读-写锁”策略,如:计数器
ThreadLocal原理
ThreadLocal是用来维护本线程的变量的,并不能解决共享变量的并发问题。ThreadLocal是 各线程将值存入该线程的map中,以ThreadLocal自身作为key,需要用时获得的是该线程之前 存入的值。如果存入的是共享变量,那取出的也是共享变量,并发问题还是存在的。
ThreadLocal的适用场景
场景:数据库连接、Session管理
2.6 Q6:OOM情况
问题:ThreadLocal什么时候会出现OOM的情况?为什么?
ThreadLocal变量是维护在Thread内部的,这样的话只要我们的线程不退出,对象的引用就会 一直存在。当线程退出时,Thread类会进行一些清理工作,其中就包含ThreadLocalMap, Thread调用exit方法如下:
&esmp; ThreadLocal在没有线程池使用的情况下,正常情况下不会存在内存泄露,但是如果使用了线程 池的话,就依赖于线程池的实现,如果线程池不销毁线程的话,那么就会存在内存泄露。
2.7 Q7:synchronized、volatile区别
问题:synchronized、volatile有什么区别
-
volatile主要应用在多个线程对实例变量更改的场合,刷新主内存共享变量的值从而使得各个 线程可以获得最新的值,线程读取变量的值需要从主存中读取;synchronized则是锁定当前变 量,只有当前线程可以访问该变量,其他线程被阻塞住。另外,synchronized还会创建一个内 存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中(即释放锁前),从而保证 了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作
-
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。 volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞,比如多个线程争抢 synchronized锁对象时,会出现阻塞。
-
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的 修改可见性和原子性,因为线程获得锁才能进入临界区,从而保证临界区中的所有语句全部得到 执行。
-
volatile标记的变量不会被编译器优化,可以禁止进行指令重排;synchronized标记的变量 可以被编译器优化。
文章来源: dpb-bobokaoya-sm.blog.csdn.net,作者:波波烤鸭,版权归原作者所有,如需转载,请联系作者。
原文链接:dpb-bobokaoya-sm.blog.csdn.net/article/details/122621594
- 点赞
- 收藏
- 关注作者
评论(0)