个人总结笔记,不具备指导作用,怕说错了说漏了带坏其他同学,有错误的地方希望热心的同学可以指出,感谢
进入正片
相信各位同学无论是在AS或者Idea中都碰到这种情况,当你安装阿里Java编程规范插件之后,每当你通过new Thread 的方式创建一个新线程的时候,编译器都会给你提示这么一段提醒:
编译器提醒我们不要显示的创建线程,推荐使用线程池。
那么为什么要使用线程池呢?
1、降低资源的消耗,通过线程的复用降低线程创建和销毁造成的消耗。
2、提高响应效率,当我们存在一个任务需要执行的时候,该任务无需等待线程的创建就可以立即执行,一定程度上节省了线程的创建和销毁的时间。
3、提高了线程的可控性,对线程数量上进行把控,避免不必要的资源消耗,对过多的任务请求也能进行调配优化(使用RejectedExecutionHandler)
介绍完毕使用线程池的原因,接下来说说使用线程池需要知道哪些基本概念,由上图可知,线程池的创建通过新建ThreadPoolE-xecutor实现,任务的执行通过execute()方法,关闭通过shutdown()方法。
首先看看ThreadPoolExecutor的构造方法
ThreadPoolExecutor(int corePoolSize,//1
int maximumPoolSize,//2
long keepAliveTime,//3
TimeUnit timeUnit,//4
BlockingQueue<Runnable> workQueue,//5
ThreadFactory threadFactory,//6
RejectedExecutionHandler rejectedExecutionHandler//7)
1、corePoolSize
线程池中的核心线程数,当线程池中目前的线程数小于核心线程数时,新建的任务会由当前的的核心线程进行执行,如果当前线程都在执行任务中,那么就会新建线程执行任务,直到当前线程数等于核心线程数。如果这时又进来新的任务,那么该任务会被塞入阻塞队列中,等待被执行。调用prestartAllCoreThreads()方法可以提前创建所有核心线程数,prestartAllCoreThreads()的逻辑如下所示。
/**
* Starts all core threads, causing them to idly wait for work. This
* overrides the default policy of starting core threads only when
* new tasks are executed.
*
* @return the number of threads started
*/
/**
* 简单翻译一下,第一句就可以说明:启动所有核心线程,使他们等待任务。
*/
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
2、maximumPoolSize
线程池中允许存在的最大线程数,如果阻塞队列也排满了任务,那么这时新进来的任务,就会创建新的线程执行任务直到线程数与最大线程数相等。
3、keepAliveTime
线程空闲时的存活时间,当线程将任务执行完毕后,允许线程存活的时间(存活的意义是为了线程的复用),此参数只在线程数大于corePoolSize时才能用,并且核心线程不会被销毁,只有非核心线程有可能被销毁,也就是最多可移除maximumPoolSize-corePoolSize个线程
4、timeUnit
这个参数只是定义keepAliveTime参数的时间单位
5、workQueue
实现BlockingQueue接口的阻塞队列,一般有ArrayBlockingQueue和LinkedBlockingQueue,当线程数超过核心线程数时,线程会进入阻塞队列进行阻塞等待。
6、threadFactory
线程工厂,通过自定义的线程工厂可以为线程命名,一般情况下使用Executors.defaultThreadFactory()默认的线程工厂就可以,默认的命名规则为:"pool-" + poolNumber.getAndIncrement() + "-thread-" + threadNumber.getAndIncrement()
7、rejectedExecutionHandler
线程池的拒绝规则,目的:当线程池内所有可用线程资源都已耗尽(核心线程、非核心线程、阻塞队列都已经塞满了),就需要采取一种方式处理接下来还想要添加进来的任务,线程池提供了四种规则:
①AbortPolicy:直接抛出异常,默认规则。
②CallerRunsPolicy:用调用者所在的线程来执行任务。
③DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务。
④DiscardPolicy:直接丢弃任务。
实际调用
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
1,
1,
1,
TimeUnit.SECONDS,
arrayBlockingQueue,
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
poolExecutor.execute(() -> System.out.println("println" + n));
常见的四种ThreadPoolExecutor
CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor
他们之前的差别是对参数的不同配置来适用于不同的应用场景。
分析完毕基本参数的意义,接下来讲讲任务的执行顺序
查看ThreadPoolExecutor.execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取线程池状态
int c = ctl.get();
//首先判断当前运行的线程是否少于corePoolSize,如果小于则尝试创建新的线程来执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//判断是否成功添加,如果失败则继续第二个if判断
return;//添加成功则返回
c = ctl.get();//添加失败再次获取线程池状态
}
//第一步添加失败后,往阻塞队列添加任务
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//第二次检测线程池是否在运行,因为添加过程中线程池状态也许会改变
if (! isRunning(recheck) && remove(command))
//线程不在运行,队列移除刚刚添加的任务,并调用RejectedExecutionHandler执行拒绝规
//则
reject(command);
//判断当前线程数是否为0,因为核心线程数也有可能为0,因此当前线程数可能存在0的情况
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
//添加阻塞队列失败,会尝试新建非核心线程执行任务
}else if (!addWorker(command, false))
//非核心线程创建失败,调用RejectedExecutionHandler执行拒绝规则
reject(command);
}
通过源码可以大概明白ThreadPoolExecutor.execute()执行时任务添加顺序:核心线程优先、阻塞队列其次、非核心线程最后。
线程池的合理选择:
待补充(因为应该怎么设置核心数、阻塞队列应该选有界的ArrayBlockingQueue还是无界的LinkedBlockingQueue、非核心数还没有一个比较明显的应用场景,以后再补充吧,以及还有AddWorker方法的解析,这个方法中涉及到CAS的相关知识,不是特别明白,还是要在学习学习。)