什么是线程池
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)。
线程池常用类和接口
在Java标准库提供了如下几个类或接口,来操作并使用线程池:
- ExecutorService接口:进行线程池的操作访问;
- Executors类:创建线程池的工具类;
- ThreadPoolExecutor及其子类:封装线程池的核心参数和运行机制
线程池的优点
- 降低资源消耗:复用已创建的线程来降低创建和销毁线程的消耗。
- 提高响应速度:任务到达时,可以不需要等待线程的创建立即执行。
- 提高线程的可管理性:使用线程池能够统一的分配、调优和监控。
线程池的执行流程
1. 提交一个新线程任务,线程池会在线程池中分配一个空闲线程,用于执行线程任务;
2. 如果线程池中不存在空闲线程,则线程池会判断当前“存活的线程数”是否小于核心线程数corePoolSize。
- 如果小于核心线程数corePoolSize,线程池会创建一个新线程(核心线程)去处理新线程任务;
- 如果大于核心线程数corePoolSize,线程池会检查工作队列;
- 如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行;
- 如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize;
- 如果当前“存活线程数”没有达到最大线程数maximumPoolSize,则创建一个新线程(非核心线程)执行新线程任务;
- 如果当前“存活线程数”已经达到最大线程数maximumPoolSize,直接采用拒绝策略处理新线程任务;
综上所述,执行顺序为:核心线程、工作队列、非核心线程、拒绝策略
线程池分类
Java标准库提供的几种常用线程池,创建这些线程池的方法都被封装到Executors工具类中。
- FixedThreadPool:线程数固定的线程池,使用Executors.newFixedThreadPool()创建;
- CachedThreadPool:线程数根据任务动态调整的线程池,使用Executors.newCachedThreadPool()创建;
- SingleThreadExecutor:仅提供一个单线程的线程池,使用Executors.newSingleThreadExecutor()创建;
- ScheduledThreadPool:能实现定时、周期性任务的线程池,使用Executors.newScheduledThreadPool()创建
FixedThreadPool线程池
线程数固定的线程池:
package com.apescource.demo06;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) {
//计算1-100w之间所有数字的累加和,每10万个数字交给一个线程处理
//创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
//创建集合,用于保存Future执行结果
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
//每十万个数字,封装成Callable线程任务,并提交给线程池
for(int i = 0;i <= 900000;i += 1000000) {
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();
//每隔一秒,检查一次线程池的任务执行状态
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;
}
}
CachedThreadPool线程池
线程数根据任务动态调整的线程池
package com.apescource.demo06;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//固定数量的线程池
public class Test2 {
public static void main(String[] args) {
//创建一个固定大小的线程池
ExecutorService executorSerive = Executors.newCachedThreadPool();
//提交六个线程任务
for(int i = 0;i < 6;i++) {
executorSerive.execute(new Task("线程"+i));
}
//关闭线程池
executorSerive.shutdown();
}
}
class Task implements Runnable{
private String taskName;
public Task(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);
}
}
ScheduledThreadPool线程池
能实现定时、周期性任务的线程池:
package com.apescource.demo06;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test3 {
public static void main(String[] args) {
ScheduledExecutorService executorSerive = Executors.newScheduledThreadPool(3);
//延迟三秒钟执行任务,执行一次
// System.out.println("当前时间:"+LocalDateTime.now());
// executorSerive.schedule(new Task("任务A"), 3, TimeUnit.SECONDS);
//延迟一秒钟后执行任务,每隔三秒钟执行一次
System.out.println("当前时间:"+LocalDateTime.now());
executorSerive.scheduleAtFixedRate(new Task("任务A"),1, 3, TimeUnit.SECONDS);
// System.out.println("当前时间:"+LocalDateTime.now());
// executorSerive.scheduleWithFixedDelay(new Task("任务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: 终止状态,该状态表示线程池彻底关闭。
线程池分类总结
FixedThreadPool
线程数固定的线程池
线程池参数:
- 核心线程数和最大线程数一致
- 非核心线程线程空闲存活时间,即keepAliveTime为0 阻塞队列为无界队列LinkedBlockingQueue
工作机制:
- 提交线程任务
- 如果线程数少于核心线程,创建核心线程执行任务
- 如果线程数等于核心线程,把任务添加到LinkedBlockingQueue阻塞队列
- 如果线程执行完任务,去阻塞队列取任务,继续执行
使用场景:
- 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
CachedThreadPool
可缓存线程池,线程数根据任务动态调整的线程池
线程池参数:
- 核心线程数为0
- 最大线程数为Integer.MAX_VALUE
- 工作队列是SynchronousQueue同步队列 非核心线程空闲存活时间为60秒
工作机制:
- 提交线程任务
- 因为核心线程数为0,所以任务直接加到SynchronousQueue工作队列
- 判断是否有空闲线程,如果有,就去取出任务执行
- 如果没有空闲线程,就新建一个线程执行
- 执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续存活下去;否则,被销毁。
使用场景:
- 用于并发执行大量短期的小任务。
SingleThreadExecutor
单线程化的线程池
线程池参数:
- 核心线程数为1
- 最大线程数也为1
- 阻塞队列是LinkedBlockingQueue
- 非核心线程空闲存活时间为0秒
使用场景:
- 适用于串行执行任务的场景,将任务按顺序执行。
ScheduledThreadPool
能实现定时、周期性任务的线程池
线程池参数:
- 最大线程数为Integer.MAX_VALUE
- 阻塞队列是DelayedWorkQueue
- keepAliveTime为0
使用场景:
- 周期性执行任务,并且需要限制线程数量的需求场景。
线程池注意事项
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式。jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过new ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
必须为线程池中的线程,按照业务规则,进行命名。可以在创建线程池时,使用自定义线程工厂规范线程命名方式,避免线程使用默认名称。