线程池面试题
线程池面试题
1.线程池的核心参数(执行原理)
难3出现4
ThreadPoolExecutor的7个核心参数
-
- corePoolSize核心线程数量3
-
- maximumPoolSize最大线程数5:(核心线程数+临时线程最大数2)
-
- keepAliveTime生存时间(临时线程的存活时间)
-
- unit时间单位(临时线程数的生存单位 秒或者毫秒)
-
- workQueue阻塞队列:没有空闲的核心线程进入阻塞队列,队慢进入临时线程执行任务
-
- threadFactory线程工厂指定线程创建,设置名字是否是守护线程
-
- handle拒绝策略
执行原理:提交任务之后先看核心线程数是否满了,如果没有满进入核心线程工作,如果核心线程满了进入阻塞队列,要是阻塞队列也满了就进入临时线程工作,要是临时线程和主线程工作结束就回去看阻塞队列有没有任务,但是如果临时线程也满了就会执行拒绝策略
四种解决策略:
-
- AbortPolicy直接抛出异常,默认的解决策略
-
- CallerRunsPolicy交给主线程执行任务
-
- DiscardOldesPolicy:删除阻塞队列中最靠前的任务
-
- DiscardPolicy:直接删除任务,就是直接删除新来的任务
2.线程中有哪些常见的阻塞队列
难3出现3
- ArrayBlockingQueue:基于数组结构的有界阻塞队列FIFO
- LinkedBlockingQueue:基于链表结构的有界阻塞队列FIFO
- DelayWorkQueue:一线队列,他可以保证每次出队都是当前队列中执行时间靠前的
- Synchronized:不存元素的阻塞队列,每个插入都必须等待一个移除操作
LinkedBlockingQueue默认无界,支持有界(一般设置) 底层是链表 是懒惰的创建节点的时候添加数据 入队会添加新的Node节点 头尾两把锁 一遍出队 一遍出队 运用的多
ArrayBlockingQueue:必须有界 底层是数组 提前初始化Node数组 Node需要提前创建 一把锁 必须给一个容量大小
3.如何确定核心线程数
难3出现3
IO密集型任务 一般文件读写 DB读写 网络请求 大多数 核心线程数2N+1
cpu密集型任务 一般来说计算型代码 Bitmap转换 Gson转换 核心线程数N+1
4.线程池种类
难3出现3
Executors中静态方法便捷的创建线程池
- 固定线程数的线程池: 任务量知道就可以用 Executors.newFixedThreadPool(3)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 单线程化的线程池 Executors.newSingleThreadExecutor()
主线程和最大线程都是1 先提交的先执行 有循序的线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 可缓存的线程池 Executors.newCachedThreadPool()
核心线程数是0 都是临时线程 任务数密集但是任务时间比较短的情况下
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 延迟和周期执行的线程池Executors.newScheduledThreadPool(3)执行延迟任务的线程
public static ScheduledExecutorService
newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public class exegesis01 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
scheduledExecutorService.schedule(() -> {
System.out.println("hello world");
}, 5, java.util.concurrent.TimeUnit.SECONDS);
}//任务 延迟时间 单位
}
5.为什么不建议使用Executors创建线程池
允许最大队列长度或者是最大线程数的最大值为int的最大值,会引起内存溢出推荐使用Threadpollexecutor
6.如何控制某个方法允许并发访问线程数量
在多线程总提供了一个Semaphore信号量 创建一个Semaphore对象,可以给一个容量acquire()可以请求一个信号量,这时候信号量个数-1 release()释放一个信号量,此时信号量个数加1
7.使用场景
你们的项目中哪些用到了线程池
- 批量导入:使用线程池+CountDownLatch批量把数据库中发数据导入
- 数据汇总:调用多个接口来汇总数据,所有接口的没有依赖关系相互独立使用线程池+future来提升性能
- 异步线程:为了避免上级方法影响下级方法可以使用异步线程来调用下一个方法
8.谈一谈对ThreadLocal的理解
多线程对于解决线程安全的一个操作类,他会为每一个线程都分配一个独立的线程副本,从而解决访问冲突的问题。实现了线程内的资源共享
set() get() remove()
底层实现原理
ThreadLocal本质来说是一个线程内部存储类,从而让多个线程只操作自己内部的值,实现线程之间的数据隔离,ThreadLocalmap就是来操作存放数据的,调用set先获取对象名字 然后判断map是否存在 要是存在就数据存储,不存在就新建。调用get方法还是先获取线程名和线程对象,判断不为空就调用getEmtry来获取数组对象。remove和get类似
内存泄漏问题 ThreadLoca的key是弱引用,内存不够就会回收,但是value是强应用,就不会回收就会有内存泄漏等等问题出现 如比使用remove来删除
该文章为java黑马程序员面试篇