面试7. 线程池用过么?
线程池的优势
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行
主要特点:线程复用,控制最大并发数,管理线程
核心都是ThreadPoolExecutor
三种线程池:newFixedThreadPool():传固定值的线程池,适合长期的任务,性能好
newSingleThreadExecutor():固定一个线程,适合依次执行任务的场景,一个一个执行任务的场景
newCachedThreadPool():可缓存线程池,执行很多短期异步的小程序或负载较轻的任务
public static void main(String[] args) {
//ExecutorService executorService = Executors.newFixedThreadPool(5);//一池5线程
//ExecutorService executorService = Executors.newSingleThreadExecutor();//一池1线程
ExecutorService executorService = Executors.newCachedThreadPool();//一池N线程,具体不确定,看处理情况
try {
for (int i = 0; i < 10; i++) {
int j = i;
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t" + j);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
源码可以看到:底层是ThreadPoolExecutor和阻塞队列LinkedBlockingQueue和同步队列SynchronousQueue
线程池的参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
7个参数
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:多余的空闲线程的存活时间
unit:keepAliveTime单位
workQueue:阻塞队列
threadFactory:线程工厂,使用默认的
handler:拒绝策略
线程池 == 银行网点
核心线程数:今日当值的受理窗口
最大线程数:最多的受理窗口
阻塞队列:银行的候客区
线程工厂:银行的logo,工作人员的胸卡,使用默认的
拒绝策略:如果最大线程数满了,阻塞队列也满了,就拒绝客户进入到网点了
如果核心线程数都用了,阻塞队列也满了,就扩大到最大线程数,处理完,如果有空闲,超过keepAliveTime就销毁掉多的线程,到核心线程数就行
线程池的工作原理
在创建线程池后,等待提交过来的任务请求
当调用了execute()方法添加一个请求任务时,线程池会做出如下判断
如果正在运行的线程数量小于corePoolSize,那么创建线程运行这个任务
如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务方法队列
如果这个时候任务队列满了且正在运行的线程数小于MaximumPoolSize,那么创建非核心线程运行任务
如果队列满了,且当前线程数大于等于maximumPoolSize,那么线程池会启动拒绝策略来执行
当一个线程完成任务时,它会从队列中取出下一个任务来执行
当一个线程无事可做,超过一定的时间,线程池会判断
如果当前运行线程数大于corePoolSize,那么这个线程会停掉,会最终收缩到corePoolSize的大小
线程池的拒绝策略
默认是抛异常
AbortPolicy(默认):直接抛出RejectedExecutionException异常,阻止系统正常运行
CallerRunsPolicy:"调用者运行"一种调节机制,该策略不会抛弃任务,也不会抛异常,而是将某些任务回退到调用者,从而降低新任务的流量
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
DiscardPolicy:直接丢弃任务,不处理也不抛异常,如果允许任务丢失,这是最好的一种方案
这几种策略,都实现了RejectedExecutionHandler接口,可以自定义拒绝策略
//自定义拒绝策略,抛异常
private static class CustomRejectPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
throw new RejectedExecutionException("异步工具类task满了,抛出异常拒绝执行任务! " + r.toString() +
" rejected from " +
executor.toString());
}
}
生产中的线程池的使用
private static int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();//核心线程数:cpu核数
private static int MAX_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;//最大线程数:cpu核数*2
private static ThreadPoolExecutor EXECUTOR_SERVICE = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAX_POOL_SIZE,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(40000*MAX_POOL_SIZE),
Executors.defaultThreadFactory(),
new CustomRejectPolicy());
实际工作中使用的是架构师写的线程池工具类。因为那几种线程池的创建,阻塞队列的最大值是Integer.MAX_VALUE,会使堆塞满了,这样会导致OOM异常
测试拒绝策略
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,2L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
//new ThreadPoolExecutor.AbortPolicy()//默认的,会抛异常
//new ThreadPoolExecutor.CallerRunsPolicy()//回退给调用线程
//new ThreadPoolExecutor.DiscardOldestPolicy()//抛弃最久的任务
new ThreadPoolExecutor.DiscardPolicy()//直接抛弃任务 );
try {
for (int i = 0; i <= 20; i++) {
int j = i;
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t" +j);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
线程池配置合理线程数
确定最大线程数的标准
cpu密集型:任务需要大量的运算,没有阻塞,cpu全速运行,尽可能减少线程数
一般公式:cpu核数+1
IO密集型:有大量的阻塞,有两种配置方式
(1)cpu核数*2
(2)cup核数 /1-阻塞系数 (阻塞系统在0.8-0.9之间)8核cpu的话,8/1-0.9=80个线程数