1、线程池简介
在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
2、创建线程池
2.1 创建线程池的构造函数
线程池类ThreadPoolExecutor的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
构造函数的参数含义分别如下:
- int corePoolSize: 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
- int maximumPoolSize : 线程数的上限
- long keepAliveTime:超过corePoolSize的线程的idle时长, 超过这个时间,多余的线程会被回收。
- TimeUnit unit: 配合keepAliveTime使用,表示时间的单位
- BlockingQueue workQueue:任务的阻塞队列
- ThreadFactory threadFactory:新线程的产生方式,也就是创建线程的工厂
- RejectedExecutionHandler handler) : 拒绝策略,在任务满了拒绝执行某些任务的策略。
下面依靠一张图来更好的理解线程池和这几个参数:
2.2 通过Executors创建线程池
Executors是一个工厂类,负责创建为我们创建线程池,主要有以下几个创建线程池的方法:
- newFixedThreadPool(int nThreads): 创建固定大小的线程池
- newSingleThreadExecutor() :创建只有一个线程的线程池
- newCachedThreadPool() :创建一个不限线程数上限的线程池,任何提交的任务都将立即执行
- newScheduledThreadPool(int corePoolSize):创建一个支持定时及周期性的任务执行的线程池
但是在开发中不推荐使用这种方式创建,阿里巴巴Java开发手册中也明确指出,而且用的词是『不允许』使用Executors创建线程池。
我们要避免使用无界队列,不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为这种方式会使用无界的任务队列,为避免OOM,我们应该使用ThreadPoolExecutor的构造方法手动指定队列的最大长度:
ExecutorService executorService = new ThreadPoolExecutor(2, 2,
0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(512), // 使用有界队列,避免OOM
new ThreadPoolExecutor.DiscardPolicy());
3、线程池的工作流程
首先来了解一下线程池的执行流程:
由图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
handler的拒绝策略有四种:
-
第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
-
第二种DisCardPolicy:不执行新任务,也不抛出异常
-
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
-
种CallerRunsPolicy:直接调用execute来执行当前任务
线程池的任务提交方式
我们知道,实现多线程有三种方式,分别是继承Thread类、实行Runnable接口、实行Callable接口,那么线程池执行任务有几种任务提交方式呢?
可以向线程池提交的任务有两种:Runnable和Callable,二者的区别如下:
- 方法签名不同:void Runnable.run(), V Callable.call() throws Exception
- 是否允许有返回值:Callable允许有返回值
- 是否允许抛出异常:Callable允许抛出异常。
三种提交任务的方式:
提交方式 | 是否有返回结果 |
---|---|
Future submit(Callable task) | 有 |
void execute(Runnable command) | 无 |
Future<?> submit(Runnable task) | 无 |
4、线程池使用示例
我们还是以火箭发射为例,看一下代码:
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,3,0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(3),new ThreadPoolExecutor.AbortPolicy());
Future futureA = threadPoolExecutor.submit(new Engine());
Future futureB = threadPoolExecutor.submit(new Fire());
Future futureC = threadPoolExecutor.submit(new Instrument());
System.out.println(futureA.get());
System.out.println(futureB.get());
System.out.println(futureC.get());
System.out.println("火箭发射");
}
static class Engine implements Callable{
@Override
public String call() {
for (int i = 3; i > 0; i--)
System.out.println("引擎检查" + i);
return "引擎检查完毕";
}
}
static class Fire implements Callable<String> {
@Override
public String call() {
for (int i = 3; i > 0; i--)
System.out.println("点火设备检查" + i);
return "点火设备检查完毕";
}
}
static class Instrument implements Callable{
@Override
public String call() {
for (int i = 3; i > 0; i--)
System.out.println("仪器检查" + i);
return "仪器检查完毕";
}
}
}
运行结果:
5、总结
ThreadPoolExecutor是线程池类,它的构造函数中有6个参数,含义分别为:
- int corePoolSize: 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
- int maximumPoolSize : 线程数的上限
- long keepAliveTime:超过corePoolSize的线程的idle时长, 超过这个时间,多余的线程会被回收。
- TimeUnit unit: 配合keepAliveTime使用,表示时间的单位
- BlockingQueue workQueue:任务的阻塞队列
- ThreadFactory threadFactory:新线程的产生方式,也就是创建线程的工厂
- RejectedExecutionHandler handler) : 拒绝策略,在任务满了拒绝执行某些任务的策略。
我们在使用线程池执行任务时,可以调用execute()方法,没有返回值,也可以调用submit(XXX)方法,则执行完后会返回一个Future对象表示线程的返回结果;在线程池使用完毕后,我们需要调用shutdown()方法来关闭线程池。