1.Executor的UML图
ScheduledThreadPoolExecutor类继承ThreadPoolExecutor,实现ScheduledExecutorService接口,ThreadPoolExecutor类继承AbstractExecutorService类,AbstractExecutorService类实现ExecutorService接口,ScheduledExecutorService接口实现ExecutorService接口,ExecutorService接口实现Executor接口。
2.创建线程池的方式
1.预定义线程池:使用Executors提供的方法创建线程池
- Executors.newFixedThreadPool(int nThreads):创建固定大小的线程池
提交一个任务创建一个线程,直至到达固定大小后不变,此时新提交的任务需等待至有空闲线程才会执行,空闲线程不会回 收。
- Executors.newSingleThreadExecutor():创建单个线程的线程池
线程池内只有一个线程,一个时间段只能执行一个任务。提交多个任务时按提交顺序顺序执行。
- Executors.newCachedThreadPool():创建可缓存的线程池
线程池上限无限大(实际最大为Integer.MAX_VALUE),提交一个任务时如果线程池内有空闲线程则复用该线程,没用则创建 新线程;但空闲线程超过最大等待时间(默认60s)还没有新任务提交时,则会回收该线程。
- Executors.newScheduledThreadPool(int corePoolSize):创建可调度(任务延时执行与任务周期性执行)的线程池
任务延时执行:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.schedule(task1,2, TimeUnit.SECONDS);
scheduledExecutorService.schedule(task2,3, TimeUnit.SECONDS);
上述创建了2个基本线程的可调度的线程池大小,task1任务会在2s后执行,task2任务会在3s后执行。
任务周期性执行:
scheduledExecutorService.scheduleAtFixedRate(task1,1,2,TimeUnit.SECONDS.SECONDS);//1s后以2s为周期执行task1;
scheduledExecutorService.scheduleWithFixedDelay(task2,1,2,TimeUnit.SECONDS.SECONDS);//1s后以2s为周期延时1秒 执行task2。
2.自定义线程池:使用ThreadPoolExecutor创建线程池
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数名 | 作用 |
corePoolSize | 核心线程池大小 |
maximumPoolSize | 最大线程池大小 |
keepAliveTime | 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间 |
unit | 时间单位 |
workQueue | 阻塞任务队列 |
threadFactory | 新建线程工厂 |
handler | 拒绝策略:当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理 |
阻塞任务队列workQueue种类:
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列
拒绝策略RejectedExecutionHandler种类:
- AbortPolicy:该策略是线程池默认策略;如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
- DiscardPolicy:如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。
- DiscarOldestPolicy:如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
- CallRunsPolicy:如果添加到线程池失败,那么主线程会自己取执行该任务,不会等待线程池的线程取执行。
- 自定义策略
import java.util.Date;
import java.util.concurrent.*;
/**
* ClassName:ThreadPool
* Description:线程池的相关使用
* Author:YMJ
* Date:2020-07-14 19:57
* Version:V1.0
* 《阿里Java开发手册》
* 【强制】新建线程时,不允许在应用中自行显式创建线程,必须通过线程池提供(AsyncTask 或者ThreadPoolExecutor或者其他形式自定义的线程池)
* 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
*/
public class ThreadPoolTest {
//线程工厂所要创建的线程类
public static class MyThread extends Thread {
public MyThread(Runnable r){
super(r);
}
public MyThread(Runnable r,String name) {
super(r, name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Date startDate = new Date();
super.run();
Date finishDate = new Date();
System.out.println(threadName+"运行耗时:"+(finishDate.getTime()-startDate.getTime()));
}
}
//线程所要执行的任务类
public static class MyTask implements Callable<Object>{
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+"任务进行中");
Thread.sleep(200);
return null;
}
}
public static class MyThreadFactory implements ThreadFactory{
private static int threadCount = 0;
@Override
public Thread newThread(Runnable r) {
threadCount++;
MyThread th = new MyThread(r,"线程"+ Integer.valueOf(threadCount).toString());
return th;
}
}
public static void main(String[] args) {
LinkedBlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<Runnable>(4);//没有指定大小时默认为Integer.MAX_VALUE
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
6,
5,
TimeUnit.SECONDS,
linkedBlockingQueue,
new MyThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//创建了一个核心线程数为2,最大线程数为6,空闲等待时间为5s,阻塞队列大小为4的链式阻塞队列,使用自定义线程工厂,采取默认拒绝策略的线程池
MyTask mytask = new MyTask();
for (int i = 1; i <= 15; i++) {
threadPool.submit(mytask);
System.out.println("阻塞队列大小:"+linkedBlockingQueue.size());
}
threadPool.shutdown();
}
}
执行流程:
1.创建了一个核心线程数为2,最大线程数为6,空闲等待时间为5s,阻塞队列大小为4的链式阻塞队列,使用自定义线程工厂,采取默认拒绝策略的线程池
2.当一次性提交15个任务时,首先执行任务1和2,当超过了corepoolsize大小时会将任务放入阻塞队列中,由于阻塞队列大小为4,于是任务3,4,5,6被放入阻塞队列
3.然后阻塞队列满了后,当前线程池内线程数如果还未超过最大线程数maximumpoosize时,线程池继续创建线程,直至线程池内线程数达到最大线程数。因此线程池还能继续创建4个线程去执行任务7,8,9,10
4.当阻塞队列已满并且线程池内线程数达到最大线程数时,则会触发拒绝策略;本例采用默认拒绝策略AbortPolicy,因此程序将抛出RejectedExecutionException异常。因此任务10,11,12,13,14,15将被丢弃并且抛出异常
线程池总容量=最大线程数+阻塞队列大小,超过后执行拒绝策略
线程池提交任务·execute()与submit()的区别:
1.execute只能添加Runnable任务,submit可以提交Runnable任务与Callable任务
2.execute无返回结果,submit有返回结果(即使添加的任务是Runnable任务也会有返回结果,虽然无意义)
3.发生异常execute会终止进程,而submit可以捕获异常
在使用线程池的时候推荐使用ThreadPoolExecutor来创建,这样一是可以更加明确线程池的运行规则,二是由于executors提供的创建线程池的方法中默认的maximumPoolSize都是Integer.MAX_VALUE,这样会导致堆积大量的请求或者创建大量的线程,造成资源耗尽OOM(out of memory)。
因此《阿里java开发手册》才有此规定:
* 【强制】新建线程时,不允许在应用中自行显式创建线程,必须通过线程池提供(AsyncTask 或者ThreadPoolExecutor或者其他形式自定义的线程池)
* 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
*/