主要用于概念扫盲和梳理。
本文主要为什么使用线程池,ThreadPoolExecutor构造函数,常见的线程池种类。
一. 为什么用线程池
1. 系统开销
创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率
例如:
记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3
如果T1+T3>T2
,那么是不是说开启一个线程来执行这个任务太不划算了!
线程池缓存线程,可复用的闲置线程来执行新任务,避免了T1+T3带来的系统开销
2. 系统资源
线程并发数量过多,抢占系统资源从而导致阻塞
我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况
运用线程池能有效的控制线程最大并发数,避免以上的问题
3. 管理线程
对线程进行一些简单的管理
比如:延时执行、定时循环执行的策略等
运用线程池都能进行很好的实现>
二. 继承关系
总览
常用的几个类或接口
ExecutorService
线程池接口,继承于Executor接口,多了Submit()
、shutdown()
等方法
ScheduledExecutorService
功能和Timer类似,解决那些需要任务重复执行的问题,负责线程的调度
ThreadPoolExecutor
线程池的实现类
ScheduledThreadPoolExecutor
继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
Executors
一个工具类
,提供几个创建线程池的方法
Future 接口
它和FutureTask类(实现了Future接口)都用来表示异步计算的结果。
三. 构造线程池
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。
- 在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类
- 对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置
构造函数
七个参数 :
心 总 时 纲 队 厂 异
五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数解释
1. int corePoolSize ---核心线程数最大值
- 线程池新建线程的时候
- 如果当前线程总数小于corePoolSize,则新建的是核心线程
- 如果超过corePoolSize,则新建的是非核心线程
- 核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
- 如果指定ThreadPoolExecutor的
allowCoreThreadTimeOut
这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉
2. int maximumPoolSize---线程总数最大值
- 线程总数 = 核心线程数 + 非核心线程数
3. long keepAliveTime---非核心线程闲置超时时长
- 一个
非核心线程
,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉 - 如果设置allowCoreThreadTimeOut = true,则会作用于核心线程
4. TimeUnit unit---量纲
keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:
单位 | 时长 |
---|---|
NANOSECONDS | 1微毫秒 = 1微秒 / 1000 |
MICROSECONDS | 1微秒 = 1毫秒 / 1000 |
MILLISECONDS | 1毫秒 = 1秒 /1000 |
SECONDS | 秒 |
MINUTES | 分 |
HOURS | 小时 |
DAYS | 天 |
5. BlockingQueue<Runnable> workQueue--任务队列
- 维护着等待执行的Runnable对象
- 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务
常用的workQueue类型:
SynchronousQueue
它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现线程数达到了maximumPoolSize而不能新建线程
的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
LinkedBlockingQueue
基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE
这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。
由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
ArrayBlockingQueue
基于数组的先进先出队列,此队列创建时必须指定大小
可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
DelayQueue
队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
6. ThreadFactory threadFactory
主要用来创建线程,是一个接口,new他的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上
AsyncTask新建线程池的threadFactory参数源码:
new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread new Thread(Runnable r) {
return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
}
}
7. RejectedExecutionHandler handler
表示当拒绝处理任务时的策略,有以下四种取值:
取值 | 策略 |
---|---|
ThreadPoolExecutor.AbortPolicy(默认) |
丢弃 任务并抛出 RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 也是丢弃 任务,但是不抛出 异常 |
ThreadPoolExecutor.DiscardOldestPolicy |
丢弃队列最前面 的任务,然后重新尝试执行任务(重复此过程) |
ThreadPoolExecutor.CallerRunsPolicy |
由调用线程处理该任务 ,线程调用运行该任务的execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度 |
ThreadPoolExecutor的策略
上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:
- 核心线程满了吗
1.1 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
1.2 线程数量达到了corePools,则将任务移入队列等待 - 队列满了吗
2.1 队列已满,新建线程(非核心线程)执行任务
2.2 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler
抛出异常
总结起来,也即是说,当有新的任务要处理时,先看线程池中的线程数量是否大于 corePoolSize,再看缓冲队列 workQueue 是否满,最后看线程池中的线程数量是否大于 maximumPoolSize。
另外,当线程池中的线程数量大于 corePoolSize 时,如果里面有线程的空闲时间超过了 keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。
ThreadPoolExecutor类中重要方法
方法 | 作用 |
---|---|
execute() | 通过这个方法可以向线程池提交一个任务,交由线程池去执行 |
submit() | 实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果 |
shutdown() | 不会立即终止线程池,而是要等 所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务 |
shutdownNow() | 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
|
四. Executor与Executors
关系
Java里面线程池的顶级接口是 Executor,不过真正的线程池接口是 ExecutorService, ExecutorService 的默认实现是 ThreadPoolExecutor;普通类 Executors 里面调用的就是 ThreadPoolExecutor。
对比
Executor 框架
Executor框架在Java 5中被引入,Executor 框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架
无限制的创建线程会引起应用程序内存溢出,所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用 Executor 框架可以非常方便的创建一个线程池。Executors 类
Executors为Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类提供了一些工具方法。Executors 可以用于方便的创建线程池。
五. 常见四种线程池
Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的
1. CachedThreadPool()--可缓存线程池
特点
- 线程数无限制
- 有空闲线程则复用空闲线程,若无空闲线程则新建线程
- 一定程度上减少频繁创建/销毁线程,减少系统开销
场合
缓存型池子通常用于执行一些生存期很短的异步型任务 因此在一些面向连接的 daemon 型 SERVER 中用得不多。
但对于生存期短的异步任务,它是 Executor 的首选。
源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2. FixedThreadPool(int nThreads)--定长线程池
特点
- 可控制线程最大并发数(同时执行的线程数)
- 超出的线程会在队列中等待
场合
FixedThreadPool 多数针对一些很稳定很固定的正规并发线程,多用于服务器。
//threadFactory => 创建线程的方法,这就是我叫你别理他的那个星期六!你还看!
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2个参数的构造方法源码,不用我贴你也知道他把星期六放在了哪个位置!所以我就不贴了,省下篇幅给我扯皮
3. ScheduledThreadPool(int corePoolSize)--定核心线程池
- 支持定时及周期性任务执行。
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
源码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
4. SingleThreadExecutor()--单线程化的线程池
- 有且仅有一个工作线程执行任务
- 所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
六. 实际例子
Executor 执行 Runnable 任务
通过 Executors 的以上四个静态工厂方法获得 ExecutorService 实例,而后调用该实例的 execute(Runnable command)方法即可。一旦 Runnable 任务传递到 execute()方法,该方法便会自动在一个线程上执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCachedThreadPool{
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newFixedThreadPool(5);
// ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++){
executorService.execute(new TestRunnable());
System.out.println("************* a" + i + " *************");
}
executorService.shutdown();
}
}
class TestRunnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName() + "线程被调用了。");
}
}
输出:(每次可能不一样)
************* a0 *************
pool-1-thread-1线程被调用了。
************* a1 *************
pool-1-thread-2线程被调用了。
************* a2 *************
pool-1-thread-3线程被调用了。
************* a3 *************
************* a4 *************
pool-1-thread-1线程被调用了。
pool-1-thread-3线程被调用了。
Executor 执行 Callable 任务
在 Java 5 之后,任务分两类:一类是实现了 Runnable 接口的类,一类是实现了 Callable 接口的类。两者都可以被 ExecutorService 执行,但是 Runnable 任务没有返回值,而 Callable 任务有返回值。并且 Callable 的 call()方法只能通过 ExecutorService 的 submit(Callable task)
方法来执行,并且返回一个 Future,是表示任务等待完成的 Future。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class CallableDemo{
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<String>> resultList = new ArrayList<Future<String>>();
for (int i = 0; i < 10; i++){ //创建10个任务并执行
//使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
Future<String> future = executorService.submit(new TaskWithResult(i));
//将任务执行结果存储到List中
resultList.add(future);
}
//遍历任务的结果
for (Future<String> fs : resultList){
try{
while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成
System.out.println(fs.get()); //打印各个线程(任务)执行的结果
}catch(InterruptedException e){
e.printStackTrace();
}catch(ExecutionException e){
e.printStackTrace();
}finally{
executorService.shutdown();
}
}
}
}
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id = id;
}
//任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行
public String call() throws Exception {
System.out.println("call()方法被自动调用!!! " + Thread.currentThread().getName());
//该返回结果将被Future的get方法得到
return "call()方法被自动调用,任务返回的结果是:" + id + " " + Thread.currentThread().getName();
}
}
输出:(每次可能不一样)
call()方法被自动调用!!! pool-1-thread-1
call()方法被自动调用!!! pool-1-thread-3
call()方法被自动调用!!! pool-1-thread-1
call()方法被自动调用!!! pool-1-thread-2
call()方法被自动调用!!! pool-1-thread-8
call()方法被自动调用!!! pool-1-thread-5
call()方法被自动调用!!! pool-1-thread-9
call()方法被自动调用!!! pool-1-thread-4
call()方法被自动调用!!! pool-1-thread-6
call()方法被自动调用!!! pool-1-thread-7
call()方法被自动调用,任务返回的结果是:0 pool-1-thread-1
call()方法被自动调用,任务返回的结果是:1 pool-1-thread-2
call()方法被自动调用,任务返回的结果是:2 pool-1-thread-3
call()方法被自动调用,任务返回的结果是:3 pool-1-thread-4
call()方法被自动调用,任务返回的结果是:4 pool-1-thread-5
call()方法被自动调用,任务返回的结果是:5 pool-1-thread-6
call()方法被自动调用,任务返回的结果是:6 pool-1-thread-7
call()方法被自动调用,任务返回的结果是:7 pool-1-thread-8
call()方法被自动调用,任务返回的结果是:8 pool-1-thread-1
call()方法被自动调用,任务返回的结果是:9 pool-1-thread-9
参考文章
线程池,这一篇或许就够了
Java并发编程:线程池的使用
官方文档
【Java多线程】线程池的工作原理详解(上)
极客学院