面试3.3_Java_线程池
参考链接
并发工具类
- CountDownLatch(倒计时器):协调多个线程间的同步。常用来控制线程等待。
- CyclicBarrier(循环栅栏):所有线程都到达屏障后,才能继续执行
- Semaphore(信号量):允许多个线程同时访问某个资源
线程池
为什么要使用线程池
- 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 增加线程的可管理性。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。
Executors类创建四种常见线程池
- newSingleThreadExecutor:单线程的线程池
- newFixedThreadPool:固定大小的线程池
- newCachedThreadPool:可缓存的线程池
- newScheduledThreadPool:大小无限的线程池,支持定时以及周期性执行任务的需求。
线程池参数怎么配置
参考资料:CPU 密集型 和 IO密集型 的区别,如何确定线程池大小?
-
计算密集型
线程数 = CPU数 + 1
,通常能实现最优的利用率。"+1"的理由
:一个经验值。计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。
-
I/O密集型
线程数 = CPU数 * 2
,不是最优线程数 = CPU数 * CPU利用率 * (任务等待时间 / 任务计算时间 + 1)
- CPU数量是确定的
- CPU使用率是目标值也是确定的
- W/C可通过
基准程序测试得出
的。
- 如果要求比较精确,可以通过压测来获取一个合理的值。
ThreadPoolExcutor参数
- corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。
- maximumPoolSize(最大线程数):线程池允许开启的最大线程数。
- workQueue(队列):用于保留任务并移交给工作线程的阻塞队列。
- threadFactory(线程工厂):线程创建工厂。
- keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。
- timeunit:keepAliveTime的单位。
- handler(拒绝策略):触发拒绝策略的情况
1)线程池运行状态不是 RUNNING
;
2)线程池已经达到最大线程数
,并且阻塞队列已满
时。
ThreadPoolExcutor运作流程
AddWorker方法
- 自旋CAS操作来将线程数加1
- **新建一个线程加入至workers队列中,并启用 **
Worker类继承自AQS
,run方法中调用ThreadPoolExecutor的runworker方法
- runWorker函数中最重要的是
getTask()
,不断从阻塞队列中取任务交给线程执行
拒绝策略
- AbortPolicy:
中止策略
。抛出异常 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。 - DiscardPolicy:
抛弃策略
。什么都不做,直接抛弃被拒绝的任务。 - DiscardOldestPolicy:
抛弃最老策略
。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。 - CallerRunsPolicy:
调用者运行策略
。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。
线程池有哪些队列
-
ArrayBlockingQueue:基于数组结构的有界阻塞队列
-
LinkedBlockingQueue:基于链表结构的有界/无界阻塞队列,吞吐量通常高于ArrayBlockingQueue。
-
DelayQueue:延迟队列,是一个任务定时周期的延迟执行的队列。
-
PriorityBlockingQueue:具有优先级的无界队列
-
SynchronousQueue:同步队列,不是真正的队列,只是线程之间移交的机制。Executors.newCachedThreadPool使用了该队列。
使用队列需要注意的地方
-
使用
有界队列
时,需要注意线程池满了后,被拒绝的任务
如何处理。 -
使用
无界队列
时,需要注意如果任务的提交速度大于线程池的处理速度,可能会导致内存溢出
。
核心线程怎么实现一直存活
在获取任务时,通过阻塞队列的 take()
方法实现的 一直阻塞(存活)。
非核心线程如何实现在 keepAliveTime 后死亡?
利用阻塞队列的方法,在获取任务时通过阻塞队列的 poll(time,unit)
方法实现的 延迟死亡。