目录
一.线程池的引入
1.存在的问题
在多线程并发的环境下,由于系统不能确定任意时刻线程的并发数量,如果每一次需要执行任务的时候都创建新的线程,然后用完立刻销毁的话,这对系统而言是一笔巨大的资源开销甚至导致资源耗尽的风险。
2.线程池的概念与优势
概念
:线程池采用池化的思想,预先创建一定数量的线程并将线程资源集中到一起,统一监督,统一调配。类似的设计还包括数据库连接池等。
优势
:线程池通过对资源的统一调配,降低了系统的资源损耗,提高了系统的响应速度。尤其在并发量巨大,同时单个线程任务时间又较短的情况下优势特别显著。
二.Java线程池的执行机制
1.总体概述
在java当中线程池的核心实现类是ThreadPoolExecutor:
,实现了AbstractExecutorService抽象类:
顶层接口Executor将任务与线程进行了解耦,开发者只需要将包含业务逻辑的Runnable对象传入Executor中,由Executor框架来实现线程的调用与任务的执行。当工作线程找不到任务可以执行的时候,就会被JVM自动回收。
注
:核心线程不会被JVM自动回收。因为线程池自身状态的维护也需要保证有一定数目的线程可供调用。
2.自身状态的维护
java线程池有两个重要的状态参数:运行状态
(runState)和线程数量
(workerCount)。
两个变量都存储在AtomicInteger原子变量ctl中,高3位保存runState,低29位保存workerCount。那么这也就意味着线程数量最多有:
即是源码里的COUNT_MASK变量:
运行状态共有五种:
状态转换流程:
执行shutdown()方法之后,线程池不再接受新的任务,但仍然会从阻塞队列中获取任务。因此,只有当阻塞队列为空并且工作线程为0时才会进入TIDYING状态。
常用
3.任务的执行
ThreadPoolExecutor执行任务的流程如图所示:
如果当前线程数未达到核心线程数,则立即调用线程执行该任务;如果已经达到核心线程数且阻塞队列未满,则添加任务到阻塞队列等待线程调用;否则判断当前线程数是否已经达到最大线程数,如果未达到,则添加线程执行任务;如果达到,则执行拒绝策略(抛出异常等)。
四种拒绝策略如下
:
AbortPolicy(抛出异常同时拒绝执行该任务,默认此种拒绝策略)
DiscardPolicy(直接丢弃任务但不抛异常)
DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
CallerRunsPolicy(交有提交任务的线程去执行,适用于所有任务都需要执行的情况)
4.线程的调度
工作线程Worker实现了Runnable接口,并含有firstTask变量。该变量用于指明线程创建后需要执行的第一个任务,如果该变量为空,即对应着核心线程的创建。
Worker线程的回收首先是通过AQS独占锁不可重入的特性来判断线程的工作状态,再来决定对Hash引用表的添加和移除等操作。
Worker线程执行任务的流程:
三.ThreadPoolExecutor构造方法
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
另外还有三种构造方法:
各个参数的意义:
corePoolSize: 线程池核心线程数
maximumPoolSize: 线程池最大线程数
keepAliveTime: 线程池中非核心线程的空闲存活时间
unit: 线程空闲存活时间单位
workQueue: 存放任务的阻塞队列
threadFactory: 设置创建线程的工厂(自定义线程工厂的名称)
handler: 线程池的拒绝策略
四.手动创建线程池
阿里代码规约不允许使用Executors的静态方法直接创建线程池:
实例代码:
package package03thread;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author 自定义线程池并传入执行对象
*/
public class ThreadPoolExecutorTest {
/**设置阻塞队列深度**/
private static int queueDeep = 2;
/**获取队列当前深度**/
private synchronized int getQueueSize(Queue queue) {
return queue.size();
}
/**开发者传入的业务对象**/
private class Task implements Runnable {
private int count;
public Task(int count) {
this.count = count;
}
@Override
public void run() {
System.out.println(Thread.currentThread()+"执行第"+count+"个任务");
try {
//模拟线程完成任务需要的时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**创建线程池**/
private void createThreadPool() {
/**
* 核心线程数为3,最大线程数为6,非核心线程最大空闲时间为2秒,使用深度为2的ArrayBlockingQueue队列
*/
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3,6,2, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueDeep));
//添加任务
for (int i = 1; i <= 10; i++) {
//阻塞队列已满
while (getQueueSize(tpe.getQueue()) >= queueDeep) {
System.out.println("队列已满,3秒后将再次尝试添加任务");
try {
//提交任务的线程休眠3秒,等待核心线程或阻塞队列空出位置
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Task t = new Task(i);
System.out.println("put:"+i);
tpe.execute(t);
}
tpe.shutdown();
}
public static void main(String[] args) {
ThreadPoolExecutorTest test = new ThreadPoolExecutorTest();
test.createThreadPool();
}
}
注
:当线程不够的时候,这里采取的是让线程休眠等待其余线程的空闲,并不是通过创建新的线程。
自定义线程池的关键之一是根据业务需求设定最适合的线程数。这一般是根据CPU的计算量和IO操作的次数共同决定的。