文章目录
线程池介绍
什么是池
什么是池?用过数据库的都知道有数据库中有一个叫连接池的东西,它们是不是都有一个池,其实他们都是差不多的,都是用来把我们要用的东西存储起来不管是连接和是线程都是把他们存储在一个容器中,用的时候再拿出来,用完之后在还回去,这就是池。
线程池的作用
一.复用线程
由于线程已经有线程池提前创建好了,所以我们并不用在去创建线程.在需要执行任务的时候去使用线程就好了,然后再执行完这个任务以后线程会重新回到池中,等待下一次分配任务,体现出了线程复用的好处
二.节省CPU的内存
不使用线程池的话就需要创建很多个线程,如果任务有上万个就需要创建上万个线程类处理这些任务,这样会浪费cpu内存。如果使用了线程池就不需要去创建这么多线程池了,我们可以去复用线程池中的线程,大大的减少了cpu内存的占用
三.统一管理
线程池中的线程都是由线程池来管理的,如果是自己一个个的去创建线程,任务重多,创建的线程也会繁多,很难进行管理
初识线程池
线程池的构造方法参数
ThreadPoolExecutor是创建线程池和核心类,如果要创建线程池必须通过它的构造方法来创建,所以让我们来看看它的构造参数吧
参数名 | 类型 | 含义 |
---|---|---|
corePoolSize | int | 核心线程数 |
maxPoolSize | int | 最大线程数 |
keepAliveTime | long | 保存存活时间 |
workQueue | BlockingQueue | 任务存储队列 |
threadFactory | ThreadFactory | 当线程池需要新的线程时,会使用threadFactory来生成新的线程 |
handler | RejectedExecutionHandler | 由于线程无法接受你所提交的任务的拒绝策略 |
corePoolSize指的是核心线程数量
线程池默认创建的线程数量
maxPool是最大线程数
在核心线程的基础上额外增加的线程数量的上限
添加线程池规则
1.如果线程数量小于corePoolSize,即使其它工作线程处于空闲状态,也会创建一个新线程来运行新任务
2.如果线程数量等于或大于corePoolSize但少于maxPoolSize,则将任务放入在队列中
3.如果队列满了的话,并且线程数量小于maxPoolSize,则会创建一个新线程来执行任务
4.如果队列满了,并且线程数量大于或等于maxPoolSize,则拒绝该任务(抛出异常)
创建线程池
采用自动创建固定线程池来做演示
//创建了一个固定的线程池 核心线程数量为4
ExecutorService executorService=Executors.newFixedThreadPool(4);
打开newFixedThreadPool方法可以看见它是通过调用ThreadPoolExecutor的构造方法创建出来了一个线程池,然后它将参数传入了构造方法中的核心线程数和最大线程数,这样我们的线程数就固定在了这个参数中
注:Executors是一个创建线程的工具类
常用线程池和它的特点
//固定线程池,nThreads:核心线程池数量和最大线程池数量
//传入的参数将为线程池最大创建线程数
newFixedThreadPool(int nThreads)
//简单线程池,简单线程池方法类部已经能创建的数量已经设死为1了
//所以简单线程池只能创建一个线程
newSingleThreadExecutor()
//缓存线程池,缓存线程池的核心线程数量为0,但是它的最大线程数
//是无上限的,所以每当有任务需要执行时都会去创建非核心线程去
//执行任务,它的线程存活时间是60s
newCachedThreadPool()
//定期执行线程,可以根据需求去设置一些特点时间需要执行的任务
newScheduledThreadPool(int corePoolSize)
//newScheduledThreadPool的核心方法
//command:需要执行的任务 delay:多少秒后会去执行 unit:时间类型
schedule(Runnable command,long delay, TimeUnit unit)
//且它还有一个会一直去执行的方法
//设置它的参数可以让线程在隔出特定时间去执行任务
//command:任务 initialDelay:任务执行时间 period:间隔时间
scheduleAtFixedRate(Runnable command,long initialDelay,
long period,TimeUnit unit);
工作队列
三种最常见的工作队列
队列 | 关键字 | 作用 |
---|---|---|
直接交接 | SynchronousQueue | 当核心线程池数满了之后不会在将任务加入在队列中了,会与最大线程池交接 |
无界队列 | LinkedBlockingQueue | 队列数将会是无界限的,因此就是任务再多也不会去创建新的非核心线程 |
有界队列 | ArrayBlockingQueue | 有界限的队列,需要自己去创建队列最大数,当队列满之后还是会去跟最大线程池交接 |
阻塞队列分析
自动创建的线程池的队列参数
FixedThreadPool和SingleThreadExecutor的Queue是LinkedBlockingQueue
CachedThreadPool使用的Queue是SynchronousQueue
怎么停止线程池
shutdown()
shutdown并不是直接停止线程池,它的作用是会告诉线程池该停止了,而线程池不会立马停止,它会吧当前线程池中已经分配的任务和队列中的任务先完成才会停止线程池。当然在此线程池也不会在去接收任何任务,如果有接
到新的任务将会采用拒绝策略(抛出异常)
isShutdown()
isShutdown其实算不上是停止线程池,它是去判断线程是否还可以继续接收任务,如果是的话返回true不是则false,搭配shutdown使用
isTerminated()
isTerminated是用来判断线程池是否已经完全结束了,已经不在执行了,它返回的也是一个boolean值
awaitTermination(long timeout, TimeUnit unit)
awaitTermination和isTerminated差不多,但是它多了2个参数作用是能在特定时间去判断线程池是否已经完全结束了
shutdownNow()
shutdownNow会直接停止线程池,但是它会将还未执行完的任务(包括队列中的任务)放入一个Runnable集合中,以致将来在进行执行执行任务
任务太多该如何拒绝(4种拒绝策略)
AbortPolicy拒绝策略
当线程池不再接收任务的时候,如果还有任务需要分配的时候会抛出一个异常(RejectedExecutionException)来告诉分配者
DiscardOldestPolicy拒绝策略
DiscardOldestPolicy这是一个抛弃策略,如字面意思所描述当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务)
DiscardOldestPolicy拒绝策略
当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中
CallerRunsPolicy拒绝策略
当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务
JDK1.8新加入的workStealingPool线程池
这个线程池和之前的都有很多的不同,它更适合去处理一些拥有子任务的任务
比如说递归(子任务),比如有一个任务现在正在执行,它底下有很多的任务,如果有其他空余的线程就会去执行它的子任务(窃取)
钩子方法,给线程池加点料
特点:
在每个任务执行完之后完成一些特定的行动(日志,统计)
代码演示
package threadlocal;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 演示每个任务执行前后放钩子函数
*/
public class PauseableTreadPool extends ThreadPoolExecutor {
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
private boolean isPaused;
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
lock.lock();
try {
while(isPaused){
unpaused.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 暂停线程
*/
private void pause(){
lock.lock();
try{
isPaused = true;
}finally {
lock.unlock();
}
}
/**
* 开启线程
*/
public void resume(){
lock.lock();
try{
isPaused = false;
unpaused.signalAll();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
PauseableTreadPool pauseableTreadPool = new PauseableTreadPool(10, 20, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
Runnable runnable = new Runnable() {
~~@Override
public void run() {
System.out.println("我被执行");
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();~~
}
}
};
for (int i = 0; i < 10000; i++) {
pauseableTreadPool.execute(runnable);
}
Thread.sleep(1500);
pauseableTreadPool.pause();
System.out.println("线程池被暂停了");
Thread.sleep(1500);
pauseableTreadPool.resume();
System.out.println("线程此被开启了");
}
}
线程池的源码部分析
Executor家族
哪个是线程池呢
Executor是顶层接口,它只有一个方法,就是用来执行任务的
ExecutorService接口继承了Excutor接口并增加了一些新的方法比如shutdown方法
AbstractEexecutorService是实现了ExecutorService
ThreadPoolExecutor才是真正的线程池
线程池实现任务复用原理
打开ThreadPoolExecutor类中找到runWorker就是执行任务的核心方法
可以看出这里从Worker中取出了任务然后去查看是否找到存在这个任务,在while里面执行了一些判断在执行run方法去执行这个任务
线程池状态
状态 | 说明 |
---|---|
RUNNING | 接受新任务并处理排队任务 |
SHUTDOWN | 不接受新任务,但处理排队任务 |
STOP | 不接受新任务,也不处理排队任务,并中断正在进行的任务 |
TIDYING | 所有任务都已终止,workerCount为零时,线程会转换到 TIDYING状态,并将运行terminate()钩子方法 |
TERMINATED | terminate()运行完成 |
使用线程池的一些注意点
避免任务堆积
避免线程数过度增加
排查线程泄漏
和ThreadLocal配合