什么是线程池
在真实的生产环境中,可能需要很多的线程来支撑整个应用,当线程数量非常多时,反而会耗尽CPU资源,如果不对线程进行控制和管理,反而会影响程序的性能,线程开销主要包括:
- 创建与启动线程的开销
- 线程销毁的开销
- 线程调度的开销
- 线程数量受限CPU处理器数量
线程池就是有限使用线程的一种常用方式,线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断地从队列中取出任务并执行。
JDK提供与线程池相关的API
JDK提供了一套Executor框架,可以帮助开发人员有效使用线程
一般使用ExecutorService
的实体类Executors
的实例方法来创建线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoor {
public static void main(String[] args) {
//创建有五个大小的线程池
ExecutorService fixedThreadPool =Executors.newFixedThreadPool(5);
//向线程池提交18个任务
for (int i = 0; i <18 ; i++) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"编号任务正在执行");
try {
Thread.sleep(2000);//模拟任务时长
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
向线程池提交了18个任务,这18个任务都存储在阻塞队列,线程池中5个线程在阻塞队列中取5个任务执行。
线程池的计划任务
ExecutorService
还有一个子接口ScheduledExecutorService
,这一个线程池可以对任务进行调度,有计划的执行某个任务。
package com;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceText {
public static void main(String[] args) {
//创建一个有调度功能的线程池
ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(10);
//在延迟两秒之后执行任务
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"--"+System.currentTimeMillis());
}
},2, TimeUnit.SECONDS);
//以固定的频率执行任务 3秒以后执行 每隔2秒执行一次
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"以固定频率开启任务"+System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);
//在上次任务结束之后,固定延迟再次执行该任务,不管任务执行多长,总是在任务结束后2秒再次执行
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"在固定频率开启任务"+System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);
}
}
scheduled
(Runnable任务,延迟时长,时间单位)给某一个线程固定的延迟scheduleAtFixedRate
(Runnable任务,延迟时长,固定频率,时间单位),如果睡眠时间超过任务等待时间,完成任务后立即执行下一个任务,如果任务等待一天,你执行耗时1.5天,超过了任务等待的时间,下一次开启不需要又等待一天,直接立即执行。scheduleWithFixedDelay
(Runnable任务,延迟时长,固定频率,时间单位),固定延迟再次执行该任务,不管任务执行多长,总是在任务结束后再次执行,如果任务等待一天,你执行耗时1.5天,任务结束后你还需要等待1.5天才能执行。
线程池的底层实现
查看Excutors工具类中newFixedThreadPool
、newSingleThreadExecutor
、newCachedThreadPool
源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
//核心线程和最大线程相同,如果多出一个会放在阻塞队列中
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
//这个线程核心线程和最大线程数都为1,在任意时刻只有一个线程在执行任务,如果有多个任务放在阻塞队列中
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
//在极端情况下,每次提交新的任务都会创建新的线程执行,适合用来执行大量耗时短并且提交频繁的任务
}
Excutors工具类中返回线程池的方法底层都使用了ThreadPoolExecutor
线程池,这些方法都是ThreadPoolExecutor
线程池的封装,ThreadPoolExecutor是ExecutorService的实现类。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
各个参数的含义:
corePoolSize
线程池中核心线程的数量maximumPoolSize
线程池中最大线程数量keepAliveTime
当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,即空闲线程在多少时间内销毁unit
实参单位workQueue
任务队列,把任务提交到该任务队列中等待执行threadFactory
线程工程用于创建线程handler
拒绝策略,当任务太多来不及处理时,如何拒绝
说明:workQueue工作队列是指提交未执行的任务队列,它是BlockingQueue接口的对象,仅用于存储Runnable接口。根据队列功能分类,在ThreadPoorExecutor构造方法可以使用以下几种阻塞队列:
- 直接提交队列,由SynchronousQueue对象提供,该队列没有容量,提交给线程池的任务不会被真是保存,总是将新的任务提交给线程执行,如果没有空闲线程则尝试创建新的线程,如果线程数量已经达到maxinumpoolsize规定的最大值则执行拒绝策略。
- 有界任务队列,由ArrayBlockingQueue实现,在创建ArrayBlockQueue对象时,可以指定一个容量,当有任务需要执行时,如果线程池种中线程数量小于CorePoolSize核心线程数则创建新的线程,如果大于核心线程数则加入等待队列,如果队列已满则无法加入,在线程数小于maxinumpoo;size指定的最大线程数前提下会创建新的线程来执行,如果线程数大于maxinumpoolsize最大线程数则执行绝策略
- 无界任务队列,由LinkBlockQueue对象实现,与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况,当有新的任务时,在系统线程数小于corepoolsize核心线程数,核心线程数则把任务加入阻塞队列
- 优先任务队列,由priorityBlockQueue实现的,是带有任务优先级的队列,是一个特殊的无界队列,不管是ArrayBlockQueue队列还是LinkedBlockQueue队列都是按照先进先出算法处理的,在priorityBlockQueue队列中可以根据任务优先级顺序先后执行
线程池的拒绝策略
ThreadPoolExecutor方法的最后一个参数指定了拒绝策略,当提交给线程池任务量超过承载能力,即线程用完了,等待队列也满了,无法为新的提交任务服务,可以通过拒绝策略来解决问题,JDK提供了四种拒绝策略。
线程池中定义了四个内部类都实现了拒绝策略继承了RejectedExecutionHandler接口
AbortPolicy策略会抛出异常
CallerRunsPolicy策略,只要线程池没有关闭,会调用线程中运行当前被丢弃的任务
DiscardPolicy策略,直接丢弃这个无法处理的任务
DiscardOldestPolicy策略,会将任务队列中最老的任务丢弃,尝试再次提交新任务
defaultHandler
是默认的拒绝策略,AbortPolicy抛出异常
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
如果内置拒绝策略午饭满足实际需求,可以扩展RejectedExecutionHandler
接口