线程池
基本概念
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。
线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待空闲状态。如果有新的线程任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,线程池会创建一个新线程进行处理或者放入队列(工作队列)中等待。
优点
- 降低资源消耗,复用已创建的线程来降低创建和销毁线程的消耗。
- 提高响应速度,任务到达时,可以不需要等待线程的创建立即执行。
- 提高线程的可管理性,使用线程池能够统一的分配、调优和监控。
执行线程任务
execute()只能提交Runnable类型的任务,没有返回值,而submit()既能提交Runnable类型任务也能提交Callable类型任务,可以返回Future类型结果,用于获取线程任务执行结果。
execute()方法提交的任务异常是直接抛出的,而submit()方法是捕获异常,当调用Future的get()方法获取返回值时,才会抛出异常。
实例代码
public static void main(String[] args) {
//计算1-100w的之间所有数字的累加和,每10w个数字交给1个线程处理
//创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
//创建集合,用于保存Future执行结果
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
//每10w个数字,封装成一个Callable线程任务,并提交给线程池
for (int i = 0; i <= 900000; i += 100000){
Future<Integer> result = executorService.submit(new CalcTask(i+1, i + 100000));
futureList.add(result);
}
try {
int result = 0;
for (int i = 0;i < futureList.size();i++){
result += futureList.get(i).get();
}
System.out.println("最终计算结果:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
executorService.shutdown();
//每隔1秒钟,检查一次线程池的任务执行状态
try {
while (!executorService.awaitTermination(1, TimeUnit.SECONDS)){
System.out.println("还没有关闭!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池关闭!");
}
//计算任务
class CalcTask implements Callable<Integer>{
private int begin,end;
public CalcTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = begin; i <= end; i++){
result += i;
}
System.out.printf("线程%s计算%d-%d范围的任务结束!\n",Thread.currentThread().getName());
return result;
}
}
线程池的执行流程
- 提交一个新线程任务,线程池会在线程池中分配一个空闲线程,用于执行线程任务;
- 如果线程池中不存在空闲线程,则线程池会判断当前“存活的线程数”是否小于核心线程数corePoolSize。
-
如果小于核心线程数corePoolSize,线程池会创建一个新线程(核心线程)去处理新线程任务;
-
如果大于核心线程数corePoolSize,线程池会检查工作队列;
-
如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行;
-
如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize;
-
如果当前“存活线程数”没有达到最大线程数maximumPoolSize,则创建一个新线程(非核心线程)执行新线程任务;
-
如果当前“存活线程数”已经达到最大线程数maximumPoolSize,直接采用拒绝策略处理新线程任务;
-
-
关闭线程池
线程池在程序结束的时候要关闭。有以下两种关闭方式:
- shutdown()方法
使用shutdown()方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭。 - shutdownNow()方法
使用shutdownNow()方法会停止正在执行的任务;
常用类和接口
在Java标准库提供了如下几个类或接口,来操作并使用线程池:
1、ExecutorService接口:进行线程池的操作访问;
2、Executors类:创建线程池的工具类;
3、ThreadPoolExecutor及其子类:封装线程池的核心参数和运行机制;
线程池的配置参数
- corePoolSize线程池核心线程数:
即线程池维护的最小线程数量,核心线程创建后不会被回收。大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收; - maximumPoolSize线程池最大线程数:
线程池允许创建的最大线程数量;(包含核心线程池数量) - keepAliveTime非核心线程线程存活时间:
当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收。 - TimeUnit时间单位:
参数keepAliveTime的时间单位; - BlockingQueue阻塞工作队列:
用来存储等待执行的任务; - ThreadFactory线程工厂 :
用于创建线程,以及自定义线程名称,需要实现ThreadFactory接口; - RejectedExecutionHandler拒绝策略:
当线程池线程内的线程耗尽,并且工作队列达到已满时,新提交的任务,将使用拒绝策略进行处理;
线程池分类
Java标准库提供的几种常用线程池,创建这些线程池的方法都被封装到Executors工具类中。
- FixedThreadPool:线程数固定的线程池,使用
Executors.newFixedThreadPool()
创建;
线程池参数:
(1)核心线程数和最大线程数一致
(2)非核心线程线程空闲存活时间,即keepAliveTime
为0
(3)阻塞队列为无界队列LinkedBlockingQueue
工作机制:
(1)提交线程任务
(2)如果线程数少于核心线程,创建核心线程执行任务
(3)如果线程数等于核心线程,把任务添加到LinkedBlockingQueue
阻塞队列
(4)如果线程执行完任务,去阻塞队列取任务,继续执行 - CachedThreadPool:线程数根据任务动态调整的线程池,使用
Executors.newCachedThreadPool()
创建;
线程池参数:
(1)核心线程数为0
(2)最大线程数为Integer.MAX_VALUE
(3)工作队列是SynchronousQueue
同步队列
(4)非核心线程空闲存活时间为60秒
工作机制:
(1)提交线程任务
因为核心线程数为0,所以任务直接加到SynchronousQueue
工作队列
(2)判断是否有空闲线程,如果有,就去取出任务执行
(3)如果没有空闲线程,就新建一个线程执行
(4)执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续存活下去;否则,被销毁。 - SingleThreadExecutor:仅提供一个单线程的线程池,使用
Executors.newSingleThreadExecutor()
创建;
线程池参数:
(1)核心线程数为1
(2)最大线程数也为1
(3)阻塞队列是LinkedBlockingQueue
(4)非核心线程空闲存活时间为0秒 - ScheduledThreadPool:能实现定时、周期性任务的线程池,使用
Executors.newScheduledThreadPool()
创建;
线程池参数:
(1)最大线程数为Integer.MAX_VALUE
(2)阻塞队列是DelayedWorkQueue
(3)keepAliveTime
为0
实例代码
public static void main(String[] args) throws InterruptedException {
//创建固定数量的线程池
//分类一:FixedThreadPool
//ExecutorService executorService = Executors.newFixedThreadPool(4)
//分类二:CachedThreadPool
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 60; i++){
executorService.execute(new Task1("线程"+i));
}
executorService.shutdown();
while (!executorService.awaitTermination(1, TimeUnit.SECONDS)){
System.out.println("线程池暂未关闭");
}
System.out.println("线程池关闭");
}
class Task1 implements Runnable{
private String taskName;
public Task1(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("启动线程 ===》 " + this.taskName);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束线程 《= " + this.taskName);
}
}
public static void main(String[] args) {
//分类三:ScheduledThreadPool
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
//延迟3秒执行任务,执行一次
// System.out.println("当前时间:" + LocalDateTime.now());
// executorService.schedule(new Task1("任务A"),3, TimeUnit.SECONDS);
//延迟1秒钟后执行任务,每隔3秒钟执行1次
System.out.println("当前时间:" + LocalDateTime.now());
executorService.scheduleAtFixedRate(new Task1("任务A"),1,3, TimeUnit.SECONDS);
System.out.println("当前时间:" + LocalDateTime.now());
executorService.scheduleWithFixedDelay(new Task1("任务A"),1,3, TimeUnit.SECONDS);
}
线程池的状态
线程池的状态分为:RUNNING , SHUTDOWN, STOP, TIDYING, TERMINATED
- RUNNING:运行状态,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。该状态的线程池会接收新任务,并处理工作队列中的任务。
- 调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态;
- 调用线程池的shutdownNow()方法,可以切换到STOP停止状态;
- SHUTDOWN:关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;
- 当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入TIDYING状态;
- STOP:停止状态,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行 的任务;
- 线程池中执行的任务为空,进入TIDYING状态;
- TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;
- terminated()执行完毕,进入TERMINATED状态;
- TERMINATED: 终止状态,该状态表示线程池彻底关闭。