为什么需要线程池
1、使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建和
销毁时造成的消耗
2、由于没有线程创建和销毁时的消耗,可以提高系统响应速度
3、通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行
线程数量的大小等
![[Pasted image 20210521173731.png]]
线程池执行所提交的任务过程:
1、核心线程池中,所有线程都在执行任务吗?
不是:创建一个线程执行刚提交的任务,
是:则进入第2步。
2、当前阻塞队列满了吗?
没满:将提交的任务放在阻塞队列中;
满了,进入第3步。
3、线程池中所有线程都在执行任务吗?
没有:创建一个新的线程来执行任务,
有:交给饱和策略进行处理。
线程池执行器的分类
ThreadPoolExecutor(基础线程池)
/**
* 创建基础线程池
*
* 线程池中数量没有固定,可达到最大值( Interger MAX VALUE )
* 线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
* 当线程池中,没有可用线程,会重新创建一个线程
* 适合比较多的任务执行周期比较短的
* ExecutorService executorService = Executors.newCachedThreadPool();
*
* 线程池中的线程处于一定的量,可以很好的控制线程的并发量
* 超出一定的量被提交时需要在队列中等待
* ExecutorService executorService = Executors.newFixedThreadPool(5);
*
* 单线程线程池
* 线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
* 相当于第二个线程池中数量填写1
* ExecutorService executorService = Executors.newSingleThreadExecutor();
*
* ----------------------------------------
*
* 提交任务
* executorService.execute(Runnable class);
*
* ----------------------------------------
*
* 关闭线程池
* executorService.shutdown();
*
*/
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i=0;i<20;i++){
executorService.execute(new Task1());
}
executorService.shutdown();
ScheduledThreadPoolExecutor(可调度的线程池)
/**
* 创建可调度的线程池
*
* 最多允许使用5个线程
* 创建一个线程池 ,它可安排在给定延迟或定时后运行命令或者定期地执行。
* 线程池中具有指定数量的线程,即便是空线程也将保留
* ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
*
* ----------------------------------------
*
* 执行方法
*
* 每2个时间单位执行一次Task1,时间单位为秒
* scheduledExecutorService.schedule(new Task1(),2, TimeUnit.SECONDS);
*
* 延迟1秒执行,每2秒执行一次,执行过程中不要关闭线程池.shutdown()
* scheduledExecutorService.scheduleAtFixedRate(new Task1(),1,2, TimeUnit.SECONDS);
*
* ----------------------------------------
*
* 关闭线程池
* scheduledExecutorService.shutdown();
*
*/
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
scheduledExecutorService.schedule(new Task1(),2, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();
ForkJoinPool
/**
* JDK1.7之后的线程池,以分而治之的思想运行,简单看看就行,太复杂了,
* 创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传如并行级别参数,将默认为当前系统的CPU个数
* 企业中用的不多
*/
//创建线程池
ExecutorService executorService = Executors.newWorkStealingPool(4);
for(int i=0;i<10;i++){
final int count = i;
//提交任务
executorService.submit(new Task1());
}
//放在这里,防止程序提前跑完
while(true){}
线程池生命周期
/**
* 线程池和线程的5种状态不一样
* 线程池生命周期只有两种状态
* 除了两种状态之外,还有3种中间状态
*
* RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
*
* SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
*
* STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。
*
* TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
*
* TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做
*/
参数含义
/**
* 创建线程池源码
* 前4个参数可以自己调,后三个默认的就可以
*
* @param corePoolSize
* 核心线程池的大小
*
* @param maximumPoolSize
* 线程池能创建线程的最大个数
*
* @param keepAliveTime
* 空闲线程存活时间
*
* @param unit
* 时间单位,为keepAliveTime指定时间单位
*
* @param workQueue
* 阻塞队列,用于保存任务的阻塞队列
*
* @param defaultThreadFactory()
* 创建线程的工程类
*
* @param defaultHandler
* 饱和策略(拒绝策略)
*/
public ThreadPoolExecutor(
int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue
){
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
阻塞队列
/**
* @param ArrayBlockingQueue(常用)
* 基于数组的阻塞队列实现
* 在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,
* 并且内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
* 而且生产者和消费者,都是共用同一个锁对象,由此也意味着两者无法真正并行。
*
* @param LinkedBlockingQueue(常用)
* 基于链表的阻塞队列,
* 同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),
* 生产者不停的生产数据,直到队列已满
* 并且生产者端和消费者端分别采用了独立的锁来控制数据同步,
* 所以在高并发的情况下生产者和消费者可以并行地操作队列中的数据,提高整个队列的并发性能。
*
* @param DelayQueue
* 只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
* 这是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,
* 而只有获取数据的操作(消费者)才会被阻塞。
* DelayQueue使用场景较少,但都相当巧妙,
* 常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队
*
* @param PriorityBlockingQueue
* 基于优先级的阻塞队列
* 不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。
* 所以要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,
* 否则时间一长,会最终耗尽所有的可用堆内存空间。
*
* @param SynchronousQueue
* 一种无缓冲的等待队列
* 拥有公平模式和非公平模式
*/
常用两种锁的不同
/**
* 1.队列中锁的实现不同
* ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;
* LinkedBlockingQueue实现的队列中的锁是分离的 ,即生产用的是putLock,消费是takeLock
*
* 2.队列大小初始化方式不同_
* ArrayBlockingQueue实现的队列中必须指定队列的大小,
* LinkedBlockingQueue实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE
*/
拒绝策略
/**
* @param ThreadPoolExecutor.AbortPolicy (最好用的)
* 丢弃任务并抛出RejectedExecutionException异常。
* 我们会知道线程池已满,可以进行处理,例如增大线程池
*
* @param ThreadPoolExecutor.DiscardPolicy
* 也是丢弃任务,但是不抛出异常。
* 我们不知道线程池是否出什么问题了,无法进行处理
*
* @param ThreadPoolExecutor.DiscardOldestPolicy
* 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* 万一前面的任务很重要呢
*
* @param ThreadPoolExecutor.CallerRunsPolicy
* 由调用线程处理该任务
* 那队列里的任务怎么办,困死在里面了
*/
execute方法执行逻辑
1. 如果当前运行的线程少于corePoolSize,则会创建新的线程来执行新的任务;
2. 如果运行的线程个数等于或者大于corePoolSize,则会将提交的任务存放到阻塞队列workQueue中;
3. 如果当前workQueue队列已满的话,则会创建新的线程来执行任务;
4. 如果线程个数已经超过了maximumPoolSize,则会使用饱和策略RejectedExecutionHandler来进行处理。
线程池的关闭
原理:遍历线程池中的所有线程,然后依次中断
1. shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
2. shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程