公众号搜索: 意姆斯Talk, 即可领取大量学习资料及实战经验
1. 概念
1.1单线程的优缺点
本文的中心不是单线程, 一句话带过: 只有累死的牛(一个线程), 没有耕坏的地(任务)
优点: 如果任务不多, 一个线程, 毫无疑问, 也是可以提高性能
缺点: 创建线程, 会频繁的去访问操作系统, 又要自己手动创建和销毁, 浪费资源, 没有一个上限的束缚, 影响性能, 浪费的是创建和销毁的这个时间!!!
1.2 线程池的简述
作用: 限制了一个线程数量, 对线程进行统一的分配,调优和监控
优点和缺点
优点: 降低资源消耗, 提高响应速度, 提高线程的可管理性, 提供定期执行的线程池, 目前没有代替线程池的东西, 我愿意称之为百利而无一害
缺点: 如何去精准地把我线程池的各个参数, 是我开发中遇到的最大问题之一!
线程池解决的是什么?
资源管理和分配问题, 为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。
2. 线程池核心设计(图非原创)
**话不多说, 数形结合是最好的老师,此处图非原创, 实属精品, 只是精简了一下, 尊重原创, 文章尾部会附加原创链接 **
公司有 正式员工 和 兼职员工 ,正常任务都是正式员工处理。当任务增加时,正式员工忙不过来了,公司会将订单暂时存到 仓库 中,等有空闲的正式员工时再处理(因为员工大部分是摸鱼空闲的, 也不会主动帮公司分担任务,所以需要管理员实时调度)。仓库满了后,任务还在增加怎么办?公司只能临时招 兼职员工 来应对任务,而兼职人员结束后是要清退的,若兼职员工也以招满后,后面的订单只能忍痛拒绝了。
管理员实时调度是源码中的getTask()方法, 下文会表明
原图如下:
好了接下来看看线程池的流程图配代码实例!!!
ThreadPoolExecutors.execute(new Runable(){......}); //表示一个当前线程数
for(int i=0;i<5;i++){
ThreadPoolExecutors.execute(new Runable(){......}); //表示五个当前线程数
}
2.1Java中的线程池核心实现类是ThreadPoolExecutor
2.2ThreadPoolExecutor四种构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
//核心构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//四选一时, 核心线程数, 最大线程数,存活时间,最大线程数<核心线程数, 抛出异常
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
//三选一时, 任务队列为空, 线程工厂为空, 拒绝策略为空, 抛出异常
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
//执行终结器时要使用的上下文,或者为null, acc是个对象, 可看源码
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
可以看到,其需要如下几个参数:
- corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
- maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
- keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
unit(必需):指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。 - workQueue(必需):任务队列。通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。
- threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
- handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
3. 线程池的两大参数之生命周期,任务队列, 拒绝策略
3.1生命周期(一张图概括)
-
RUNNING:当创建线程池后,初始时,线程池处于RUNNING状态;
-
SHUTDOWN: 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
-
STOP: 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
-
TERMINATED: 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount), 在源码中常见的两个变量, 代表性代码:
通过状态和线程数生成ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
3.2任务队列
队列: 先进先出
队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
3.3拒绝策略
任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。策略是一个接口,
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
- AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
- CallerRunsPolicy:直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。
- DiscardPolicy:直接丢弃任务,不抛出任何异常。
- DiscardOldestPolicy:将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。
4. Executors封装线程池
1、FixedThreadPool
固定容量线程池。其特点是最大线程数就是核心线程数,意味着线程池只能创建核心线程,keepAliveTime为0,即线程执行完任务立即回收。任务队列未指定容量,代表使用默认值Integer.MAX_VALUE。适用于需要控制并发线程的场景。
2、 SingleThreadExecutor
单线程线程池。特点是线程池中只有一个线程(核心线程),线程执行完任务立即回收,使用有界阻塞队列(容量未指定,使用默认值Integer.MAX_VALUE)
3、 ScheduledThreadPool
定时线程池。指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列。这是一个比较特别的线程池,适用于执行定时或周期性的任务。
4、CachedThreadPool: 缓存线程池。没有核心线程,普通线程数量为Integer.MAX_VALUE(可以理解为无限),线程闲置60s后回收,任务队列使用SynchronousQueue这种无容量的同步队列。适用于任务量大但耗时低的场景。**
5. 线程池源码解读
待续…