一、线程池解决的问题
线程的创建销毁太频繁,会额外消耗一些系统资源。
池化技术,线程复用,解决线程频繁创建和销毁的问题。
更好的管理,可以线程的使用情况,以及任务的处理数量。
也可以通过线程池来解决,任务执行前后追加一些额外的内容。
线程池有一个很大的问题:线程池的参数不好设置。
- 设置多了,频繁的上下文切换,影响性能。反而效率更低了。
- 设置少了,服务器的硬件资源利用不全。
你要系统的把线程池的核心源码全部搞定,对线程池的执行流程和细节都一个非常好的把控。然后再结合任务的类型出发,去设置更合理的核心参数。
二、线程池的核心参数(常识)
这七个参数,非常重要,一定要记住。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
1、corePoolSize:核心线程数,线程池里的常住户,默认情况下,不会被回收,有活就干,没活闲着
2、maximumPoolSize:最大线程数,指定了当前线程池中一共有多少工作线程。
核心线程数 + 非核心线程数 == 最大线程数
3、keepAliveTime: 最大空闲时间,非核心线程在空闲了一段时间后,会被干掉,默认只针对非核心线程
4、unit: 最大空闲时间的单位,纳秒,微秒,毫秒等等单位
5、workQueue: 工作队列,是存放任务的,当任务处理不过来时,将任务放到工作队列排会队~
核心线程都在忙,再有任务投递过来,就直接扔到工作队列
核心线程都构建完毕了,再有任务投递过来,就直接扔到工作队列
6、threadFactory: 线程工厂,由咱们指定Thread对象的构建,可以设置一些thread的信息。
7、handler: 拒绝策略,当工作线程都在干活,工作队列也扔满了,此时再投递任务到线程池,就拒绝当前任务。
拒绝的方式很多:默认提供了4中常用的拒绝策略。
}
三、线程池核心属性
后面要看线程池的源码,线程池的源码内会涉及到多次获取核心属性的内容。
// 要查看线程池的核心属性,就一个,ctl
// 默认是AtomicInteger,就是基于CAS实现的一个原子类,仅此而已,你就看成他是int类型的一个数值
// ctl维护了两个信息
// 1、标识着当前线程池的状态
// 2、标识这当前线程池中的工作线程个数 工作线程数 == 核心线程 + 非核心线程
// int类型在计算机中,占用了32个bit位。
// 高3位:线程池状态。 低29位:工作线程个数
private int ctl = 0;
// Integer.SIZE == 32,是int类型占用的bit位个数
private static final int COUNT_BITS = 29;
// CAPACITY:工作线程的最大个数
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
00000000 00000000 00000000 00000001
<< 29
00100000 00000000 00000000 00000000
-1
00011111 11111111 11111111 11111111 == CAPACITY
// 线程池状态的信息
// 111:代表RUNNING状态
private static final int RUNNING = -1 << COUNT_BITS;
// 000:代表SHUTDOWN状态
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001:代表STOP状态
private static final int STOP = 1 << COUNT_BITS;
// 010:代表TIDYING状态
private static final int TIDYING = 2 << COUNT_BITS;
// 011:代表TERMINATED状态
private static final int TERMINATED = 3 << COUNT_BITS;
四、线程池的执行流程
当任务提交给线程池,具体的流程是如何走的。
从图中可以看到,任务提交到线程池有4种处理方式
planA:直接创建核心线程处理任务
planB:将任务扔到工作队列
planC:创建非核心线程去处理任务
planD:忙不过来,执行拒绝策略
五、线程池的execute方法执行原理
execute的核心流程
// 任务提交给线程池后,处理的流程
public void execute(Runnable command) {
// 健壮性判断,任务不允许为null。
if (command == null)
throw new NullPointerException();
// 获取核心属性ctl(线程池状态,工作线程个数)
int c = ctl.get();
// workerCountOf获取工作线程个数
// 工作线程个数 < 核心线程数
if (workerCountOf(c) < corePoolSize) {
// 满足上述要求,创建核心线程(command,true)
// 创建工作线程成功,返回true,反之,返回false
if (addWorker(command, true))
// 工作线程创建成功,任务交给他执行,方法直接结束。
return;
// 到这,代表创建工作线程失败,重新获取一次ctl,因为前面成功的addWorker会修改ctl的值
c = ctl.get();
}
// isRunning判断当前线程池状态是否是RUNNING,是RUNNING返回true
// workQueue.offer(command),将任务扔到工作队列排队。
// 任务扔到工作队列返回true,如果满了没扔进去,返回false
if (isRunning(c) && workQueue.offer(command)) {
// 任务已经扔到工作队列了。
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 创建非核心线程,将任务交给其处理。
// 如果创建非核心线程成功,方法结束了
else if (!addWorker(command, false))
// 创建非核心线程失败
reject(command);
}
拒绝策略有哪些?
拒绝策略,记住4种线程池自带的:
1、AbortPolicy,直接抛异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
2、CallerRunsPolicy,谁提交任务到线程池,谁执行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
3、DiscardPolicy,嘛也不管,任务丢了。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
4、DiscardOldestPolicy,将最早提交到工作的队列丢弃掉,重新尝试将当前任务塞进去
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
// 将工作队列中最靠前的取出来
e.getQueue().poll();
// 再执行execute
e.execute(r);
}
}
5、自定义策略,直接声明class,实现RejectedExecutionHandler接口,重写rejectedExecution,想干啥干啥~
任务扔到工作队列后,做了什么处理?
任务扔到工作队列后,再次判断当前线程池状态是否是RUNNING
- 如果不是RUNNING,任务不能接收。将任务从工作队列移除,并且执行拒绝策略。
- 如果是RUNNING,需要再次确认有没有工作线程,如果工作线程是0个,需要构建一个非核心线程去处理工作队列中的任务,避免出现任务饥饿。
if (isRunning(c) && workQueue.offer(command)) {
// 任务已经扔到工作队列了。
// 重新拿一次ctl,recheck代表重新检查。
int recheck = ctl.get();
// if的判断:是否有并发操作,导致任务刚扔到工作队列,线程池状态就不是RUNNING,
// 如果是,需要将任务移除并且执行拒绝策略
// 线程池是RUNNING状态嘛?
// 如果不是RUNNING,返回false,执行&&后面的操作,将任务从阻塞队列移除。
// remove如果成功,返回true,失败返回false
// 如果remove成功,进入if
if (!isRunning(recheck) && remove(command))
// 执行拒绝策略。
reject(command);
// 线程池状态没问题。是RUNNING。
// 工作线程个数是不是0个啊?
// 此时会出现,如果工作线程为0个,工作队列中有任务。
// 任务饥饿,没工作线程处理
else if (workerCountOf(recheck) == 0)
// 创建非核心线程来处理任务
addWorker(null, false);
}
六、线程池的addWorker添加工作线程
addWorker看名字就知道,创建工作线程的
addWorker(参数1,参数2)
- 参数1:任务,需要线程池处理的任务
- 参数2:是否是核心线程
在addWorker里有两步操作
操作1:
- 先判断线程池状态,是否可以创建工作线程
- 再判断工作线程个数,是否满足构建工作线程的要求
操作2:操作1的判断通过了,才会执行操作2
- 构建工作线程 ---- 线程工厂
- 启动工作线程 ---- thread.start();
源码后面会逐行分析………………
问题1:核心线程数是初始直接全部创建出来的吗 还是需要在创建啊
需要再创建。懒加载的效果
问题2:来的外包(非核心线程)是从队列里拿,还是直接给外包干?
都有可能。
问题3:核心线程空闲了,再投递一个任务,任务是扔工作队列,还是交给核心线程直接处理?
看核心线程数和工作线程数。
如果核心线程数构建满了,扔工作队列。
如果核心线程数没构建到位,再次构建新的核心线程去处理。
问题4:线程池有空闲线程 但是没满还会创建吗
会