介绍
线程池主要是为了解决两个问题:
- 执行大量的异步任务时线程池可以提供较好的性能。如果不使用线程池,需要new一个线程运行,线程创建和销毁是需要开销,而线程池的线程是可以复用的
- 线程池提供了一种资源限制和管理的手段,线程的个数,动态新增线程等
Executor 框架
Executor框架是Java5之后引进的,通过Executor来启动线程与使用Thread的start方法更好,易于管理,效率更好
Executor框架接口(三大部分)
- 任务(Runnable、Callable)
执行任务需要实现的Runnable接口或Callable接口。Runnable 接口或 Callable 接口 实现类都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行
- 任务的执行(Executor)
任务执行的核心为Executor 和 ExecutorService接口, ThreadPoolExecutor和ScheduledThreadPoolExecutor两个关键的实现类
- 异步计算的结果(Future) Future接口以及Future接口的实现类FutureTask类可以代表异步计算的接口。当我们把Runnable和Callable接口的实现类提交给ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。调用submit() 方法会返回一个FutureTask对象
Executor框架的使用
- 主线程首先要创建Runnable或者Callable接口的任务对象
- 把创建的任务对象交给ExecutorService执行
ExecutorService
void execute(Runnable command);
Future<T> submit(Callable<T> task);
Future<T> submit(Runnable task, T result);
- ExecutorService.submit() 将返回一个实现Future接口的对象 FutureTask
- FutureTask.get()方法来等待任务执行完成,主线程也可以执行FutureTask.cancel()来取消任务的执行
ThreadPoolExecutor类
线程池实现类ThreadPoolExecutor是Executor框架最核心的类, 其构造方法如下:
public ThreadPoolExecutor(int corePoolSize, // 线程池的核心线程数量
int maximumPoolSize, // 线程池的最大线程数
long keepAliveTime, // 当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列,用来存储等待执行任务的队列
ThreadFactory threadFactory, // 线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler) { // 拒接策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor 参数
- corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
- maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
- workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。
- keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
- unit : keepAliveTime 参数的时间单位。
- threadFactory :executor 创建新线程的时候会用到。
- handler :饱和策略。关于饱和策略下面单独介绍一下。
饱和策略
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时
- ThreadPoolExecutor.AbortPolicy: 抛出RejectedExecutionException来拒接新任务的处理
- ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
- ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使用的是 ThreadPoolExecutor.AbortPolicy。在默认情况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列。
使用ThreadPoolExecutor构造函数创建线程池
使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。不允许使用Executors去创建线程池,而是通过ThreadPoolExecutor构造函数去创建,规避资源耗尽的风险。
- FixedThreadPool和SingleThreadExecutor:允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM
- CachedThreadPool和ScheduledThreadPool:允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM
ThreadPoolExecutor 简单使用
public class ThreadPoolExecutorDemo {
private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 10; i++) {
Runnable worker = new MyRunnable("" + i);
executor.execute(worker);
}
executor.shutdown();
while(!executor.isTerminated()){
}
System.out.println("Finished all threads");
}
}
public class MyRunnable implements Runnable{
private String command;
public MyRunnable(String s){
this.command = s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "Start. Time = " + new Date());
processCommand();
System.out.println(Thread.currentThread().getName() + "End. Time = " + new Date());
}
private void processCommand(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return super.toString();
}
}
pool-1-thread-1Start. Time = Sun Mar 01 11:06:55 CST 2020
pool-1-thread-3Start. Time = Sun Mar 01 11:06:55 CST 2020
pool-1-thread-2Start. Time = Sun Mar 01 11:06:55 CST 2020
pool-1-thread-4Start. Time = Sun Mar 01 11:06:55 CST 2020
pool-1-thread-5Start. Time = Sun Mar 01 11:06:55 CST 2020
pool-1-thread-1End. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-1Start. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-3End. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-3Start. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-2End. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-2Start. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-4End. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-4Start. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-5End. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-5Start. Time = Sun Mar 01 11:07:01 CST 2020
pool-1-thread-1End. Time = Sun Mar 01 11:07:06 CST 2020
pool-1-thread-3End. Time = Sun Mar 01 11:07:06 CST 2020
pool-1-thread-2End. Time = Sun Mar 01 11:07:06 CST 2020
pool-1-thread-4End. Time = Sun Mar 01 11:07:06 CST 2020
pool-1-thread-5End. Time = Sun Mar 01 11:07:06 CST 2020
Finished all threads
ThreadPoolExecutor简单原理
Runnable VS Callable
- Runnable 不会返回结果或抛出检查异常,Callable可以
- Executors可以实现Runnable和Callable对象之间的转换
execute() VS submit()
- execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否
- submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
shutdown() VS shutdownNow()
- shutdown(): 关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。
- shutdownNow(): 关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List
isTerminated() VS isShutdown()
- isShutDown(): 当调用 shutdown() 方法后返回为 true。
- isTerminated(): 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true
Callable 和 ThreadPoolExecutor 简单使用
public class CallableDemo {
private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;
public static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy()
);
List<Future<String>> futures = new ArrayList<>();
Callable<String> callable = new MyCallable();
for (int i = 0; i < 10; i++) {
Future<String> future = executor.submit(callable);
futures.add(future);
}
for (Future<String> fut : futures) {
try {
System.out.println(df.format(new Date()) + " : " + fut.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
//关闭线程池
executor.shutdown();
}
}
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return Thread.currentThread().getName();
}
}
2020-03-01 12:59:03 : pool-1-thread-1
2020-03-01 12:59:04 : pool-1-thread-2
2020-03-01 12:59:04 : pool-1-thread-3
2020-03-01 12:59:04 : pool-1-thread-4
2020-03-01 12:59:04 : pool-1-thread-5
2020-03-01 12:59:04 : pool-1-thread-3
2020-03-01 12:59:05 : pool-1-thread-2
2020-03-01 12:59:05 : pool-1-thread-5
2020-03-01 12:59:05 : pool-1-thread-1
2020-03-01 12:59:05 : pool-1-thread-4
常见的几种线程池
- FixedThreadPool详解
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
corePoolSize 和 maximumPoolSize 都设置为nThreads
- 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务
- 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 LinkedBlockingQueue
- 线程池中的线程执行完 手头的任务后,会在循环中反复从 LinkedBlockingQueue 中获取任务来执行
缺点: 使用的队列LinkedBlockingQueue是无限队列,因此不存在任务队列满的情况,运行中FixedThreadPool不会拒接任务,在任务比较多的时候会导致OOM(内存溢出)
- SingleThreadPool详解
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
只有一个线程的线程池 corePoolSize 和 maximumPoolSize 都设置为1
缺点:SingleThreadExecutor 使用无界队列 LinkedBlockingQueue 作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。SingleThreadExecutor 使用无界队列作为线程池的工作队列会对线程池带来的影响与 FixedThreadPool 相同。说简单点就是可能会导致 OOM
- CachedThreadPool详解
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool 的corePoolSize 被设置为空(0),maximumPoolSize被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。
- SingleThreadPool详解
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。