Java线程的创建、销毁和线程间切换是一件比较耗费计算机资源的。如果我们需要用多线程处理任务,并频繁的创建、销毁线程会造成计算机资源的无端浪费,因此项目中会使用是线程池技术。
一、线程池介绍
1、线程池的优势与结构
(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,任务可以不需要等待新线程的创建便能立即执行;
(3)提高线程的可管理性。方便线程并发数的管控。如果线程无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,可能也会导致内存占用过多而产生OOM。
(4)提供更强大的功能,延时定时线程池。
2、线程池生命周期
查看 ThreadPoolExecutor的源码:
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态。
状态 | value | 说明 |
---|---|---|
RUNNING(当线程池创建出来的初始状态) | 111 | 能接受任务,能执行阻塞任务,能执行正在执行的任务 |
SHUTDOWN(调用shutdown方法) | 000 | 不接受新任务,能执行阻塞任务 ,能执行正在执行的任务 |
STOP(调用shutDownNow) | 001 | 不接受新任务,打断正在执行的任务,丢弃阻塞任务 |
TIDYING(中间状态) | 010 | 任务全部执行完,活动线程也没了 |
TERMINATED(终结状态) | 011 | 线程池终结 |
这几种状态的转换过程:
(1)线程池在构造前(new操作)是初始状态,一旦构造完成线程池就进入了执行状态RUNNING。
严格意义上讲线程池构造完成后并没有线程被立即启动,只有进行“预启动”或者接收到任务的时候才会启动线程。但是线程池是出于运行状态,随时准备接受任务来执行。
(2)线程池运行中可以通过 shutdown() 和 shutdownNow() 来改变运行状态。
线程池Executor是异步的执行任务,因此任何时刻不能够直接获取提交的任务的状态。这些任务有可能已经完成,也有可能正在执行或者还在排队等待执行。
- shutdown()是一个平缓的关闭过程,线程池停止接受新的任务,同时等待已经提交的任务执行完毕,包括那些进入队列还没有开始的任务,这时候线程池处于 SHUTDOWN状态;
- shutdownNow()是一个立即关闭过程,线程池停止接受新的任务,同时线程池取消所有执行的任务和已经进入队列但是还没有执行的任务,这时候线程池处于 STOP状态。
(3)一般情况下我们认为shutdown()或者shutdownNow()执行完毕,线程池就进入TERMINATED状态,此时线程池就结束了。其实,在shutdown/stop到TERMINATED状态之间还存在一个TIDYING状态。
这几个状态的转化关系为:
调用shundown()方法线程池的状态由RUNNING——>SHUTDOWN
调用shutdowNow()方法线程池的状态由RUNNING——>STOP
当任务队列和线程池均为空的时候 线程池的状态由STOP/SHUTDOWN——–>TIDYING
当terminated()方法被调用完成之后,线程池的状态由TIDYING———->TERMINATED状态
线程池中控制线程池生命周期的几个方法:
3、线程池提交任务流程
1)判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。已满则判断任务队列是否已满
2)判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则判断整个线程池是否已满
3)判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和策略。
(判断线程池中当前线程数是否大于核心线程数,如果小于,在创建一个新的线程来执行任务,如果大于则。判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。判断线程池中当前线程数是否大于最大线程数,如果小于,则创建一个新的线程来执行任务,如果大于,则执行饱和策略。)
4、线程池的几个主要参数
ThreadPoolExecutor 类中参数最多的构造方法:
1)corePoolSize(核心线程池大小):核心线程池大小, 新的任务到线程池后,若线程池已创建的线程数小于corePoolSize,即便存在空闲线程,线程池会创建新的线程,直到核心线程池已满。
2)maximumPoolSize(最大线程池大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3)keepAliveTime(线程保持存活时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4)unit(参数的时间单位)线程存活时间的单位
5)workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
6)threadFactory(线程工厂):创建新线程。主要用于给任务线程起个自定义名字
7)handler(线程饱和策略):当线程池和队列都满了,再加入线程时会执行此策略。
AtomicInteger atomicInteger = new AtomicInteger(0);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,
3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
(r) -> {
return new Thread(r, "t" + atomicInteger.incrementAndGet());
}, new ThreadPoolExecutor.AbortPolicy());
corePoolSize 和 maximumPoolSize:
maximumPoolSize 包含 corePoolSize的数量。
假设都满了的情况下,上面线程池的总线程大小为 5(2+3)个。
默认情况下线程池中的线程初始时为 0,还没有创建线程,当一个新任务提交给线程池后,线程池会创建一个新线程来执行任务,
当线程数达到 核心线程数上限,这时再加入任务,新加的任务会被加入队列当中,
如果任务超过了队列(前提是有界队列)大小时,会创建 maximumPoolSize - corePoolSize 数目的线程数目作为空闲线程来执行任务,
如果线程到达 maximumPoolSize之后,仍然有新任务,这时会执行拒绝策略对新任务进行相应拒绝处理。
threadFactory:创建新线程。主要用于给任务线程起个自定义名字。
threadFactory创建的线程也是采用 new Thread()方式,threadFactory创建的线程名都具有统一的风格,默认线程名为:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
BlockingQueue<Runnable> 常用的有如下几种队列:
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
RejectedExecutionHandler handler,JDK提供的几种拒绝策略:
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务,这时,就需要用到拒绝策略这个参数。
我们也可以自己实现 RejectedExecutionHandler 接口来自定义策略。
- AbortPolicy(默认策略):表示无法处理新任务时抛出异常,
- CallerRunsPolicy:用调用者所在线程来运行任务。
- DiscardOldestPolicy: 该策略将丢弃最老的一个请求,也就是丢弃即将被执行的任务,并尝试再次提交当前任务。
- DiscardPolicy:不处理,丢弃掉
二、工厂方法
Executors类提供了4种不同的创建线程池的构造方法:
- newCachedThreadPool
- newFixedThreadPool
- newScheduledThreadPool
- newSingleThreadExecutor
统一缺点:不支持自定义拒绝策略。
阿里的java开发手册中提到:
1、Executors.newFixedThreadPool
因为,核心线程数 = 最大线程数,说明没有救急线程将被创建,因此也无需超时时间
阻塞队列是无界的,可以放任意数量的任务
适用于任务量已知,相对耗时的任务
- 优点:创建一个固定大小线程池,超出的线程会在队列中等待。
- 缺点:不支持自定义拒绝策略,大小固定,难以扩展。
@Slf4j(topic = "enjoy")
public class TestThreadExecutorPool3 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Future<String>> list = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
Future<String> futureTeak = executorService.submit(() -> {
log.debug("线程 {} -> success", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
return "success";
});
list.add(futureTeak);
}
executorService.shutdown();
log.debug("main -- start");
for (Future<String> futureTeak : list) {
try {
String result = futureTeak.get(60, TimeUnit.SECONDS);
if("success".equals(result)){
log.debug("result[{}]", result);
}else {
log.debug("result[失败]", result);
}
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("main -- end");
}
}
2、Executors.newCachedThreadPool
创建一个可缓存线程池。
核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,全部都是空闲线程60s后回收
一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到线程池中,
如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
因此,长时间保持空闲的线程池不会使用任何资源,这种线程池比较灵活。
对于执行时间短,异步任务比较多的程序而言,使用它通常可提高程序性能。
- 优点:很灵活,弹性的线程池线程管理,用多少线程给多大的线程池,不用后及时回收,用则新建
- 缺点:一旦线程无限增长,会导致内存溢出。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
ExecutorService executorService = Executors.newCachedThreadPool((r) -> {
return new Thread(r, "t" + atomicInteger.incrementAndGet());
});
log.debug("main -- start");
for (int i = 1; i < 3; i++) {
executorService.execute(() -> {
log.debug(" ==task==");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
log.debug("main -- start");
}
3、Executors.newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
任务数多于 1时,会放入无界队列排队,任务执行完毕,这唯一的线程也不会被释放。
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改。
- 优点:创建一个单线程的线程池,保证线程的顺序执行
- 缺点:不适合并发。
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
log.debug("main -- start");
for (int i = 1; i < 3; i++) {
if(i == 2){
executorService.execute(() -> {
log.debug(" ==异常 task==");
int r = 10/0;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}else {
executorService.execute(() -> {
log.debug(" ==task==");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
executorService.shutdown();
log.debug("main -- end");
4、Executors.newScheduledThreadPool
- 优点:创建一个固定大小线程池,可以定时或周期性的执行任务。
- 缺点:任务是单线程方式执行,一旦一个任务失败其他任务也受影响。
//delay 正在的延迟执行
private static void withFixedDelay() {
scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.scheduleWithFixedDelay(()->{
log.debug("start---task1");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
},2,1,TimeUnit.SECONDS);
}
//period 是包含在线程的执行时间当中的
private static void atFixedRate() {
scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.scheduleAtFixedRate(()->{
log.debug("start---task1");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
},2,1,TimeUnit.SECONDS);
}
5、提交任务的方式
- void execute(Runnable command); 提交一个线程任务,没有返回值。
- Future<?> submit;提交一个线程任务,有返回值。
- List<Future<T>> invokeAll;提交所有的任务
- T invokeAny;提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
execute和submit 上面有demo,就不写了。
invokeAll:
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Future<String>> futures = executorService.invokeAll(Arrays.asList(
()->{
log.debug("1");
return "1";
},()->{
log.debug("2");
return "2";
},()->{
log.debug("3");
return "3s";
},()->{
log.debug("4");
return "4s";
}
));
log.debug("main start");
futures.forEach(f->{
try {
log.debug(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
log.debug("main end");
}
invokeAny:
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(4);
String task = executorService.invokeAny(Arrays.asList(
() -> {
TimeUnit.MILLISECONDS.sleep(3000);
log.debug("1111");
return "task1";
}, () -> {
TimeUnit.MILLISECONDS.sleep(500);
log.debug("2222");
return "task2";
}, () -> {
TimeUnit.MILLISECONDS.sleep(2000);
log.debug("3333");
return "task3";
}, () -> {
TimeUnit.MILLISECONDS.sleep(2000);
log.debug("4444");
return "task4";
}
));
log.debug("main start");
log.debug(task);
log.debug("main end");
}
三、线程池的使用
1、ThreadPoolExecutor 类创建线程池
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//设置核心池大小
int corePoolSize = 5;
//设置线程池最大能接受多少线程
int maximumPoolSize = 10;
//当前线程数大于corePoolSize、小于maximumPoolSize时,超出corePoolSize的线程数的生命周期
long keepAliveTime = 100;
//设置时间单位是秒
TimeUnit unit = TimeUnit.SECONDS;
//设置线程池缓存队列的排队策略为FIFO,并且指定缓存队列大小为5
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5);
// 设置线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 设置线程饱和策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
//创建ThreadPoolExecutor线程池对象,并初始化该对象的各种参数
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
//往线程池中循环提交线程
for (int i = 0; i < 15; i++) {
//创建线程任务对象
ThreadTask threadTask = new ThreadTask(i);
//开启线程
threadPoolExecutor.execute(threadTask);
//获取线程池中线程的相应参数
System.out.println("线程池中线程数目:" + threadPoolExecutor.getPoolSize()
+ ",队列中等待执行的任务数目:"+ threadPoolExecutor.getQueue().size()
+ ",已执行完的任务数目:"+ threadPoolExecutor.getCompletedTaskCount());
}
//待线程池以及缓存队列中所有的线程任务完成后关闭线程池。
threadPoolExecutor.shutdown();
}
public static class ThreadTask implements Runnable{
private int num;
public ThreadTask(int num) {
this.num = num;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "==正在执行task " + num);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "==正在完毕 " + num);
}
}
}
2、ThreadPoolExecutor 类创建线程池,自定义阻塞队列
通过研究源码我们发现,ThreadPoolExecutor中阻塞队列添加任务时采用的是offer方法,如下图所示:
我们都知道,LinkedBlockingQueue、ArrayBlockingQueue以及其他阻塞队列,offer方法在添加元素的时候,如果队列已满无法添加元素的时候,offer方法会直接返回false,这就会导致上面的异常。我们自定义没重写 offer方法。
@Component
public class ThreadPoolServcieTask {
private final static Logger logger = LogManager.getLogger(ThreadPoolServcieTask.class);
@Scheduled(cron = "0/30 * * * * ?")
public void execute() {
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 200; i++) {
list.add(i);
}
CustomeBlockingQueue customeBlockingQueue = new CustomeBlockingQueue(list.size());
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(25, 50, 3, TimeUnit.SECONDS, customeBlockingQueue);
for (Integer integer : list) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(String.format("线程名:%s, 执行id=%s", Thread.currentThread().getName(), integer));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
if(!threadPoolExecutor.isShutdown()){
threadPoolExecutor.shutdown();
System.out.println("threadPoolExecutor 线程池关闭");
}
// while (true) {
// System.out.println("已执行完的任务数目:" + threadPoolExecutor.getCompletedTaskCount());
// if (1000 == threadPoolExecutor.getCompletedTaskCount()) {
// threadPoolExecutor.shutdown();
// return;
// }
// }
}
/**
* 自定义阻塞队列
*
* @param <E>
*/
public class CustomeBlockingQueue<E> extends LinkedBlockingQueue<E> {
public CustomeBlockingQueue(int maxSize) {
super(maxSize);
}
@Override
public boolean offer(@NotNull E e) {
try {
put(e);
return true;
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
return false;
}
}
}
四、ScheduledThreadPoolExecutor类的使用
1、ScheduledThreadPoolExecutor类创建线程
ScheduledThreadPoolExecutor实现了ScheduledExecutorService接口,该接口定义了可延时执行异步任务和可周期执行异步任务的特有功能。
给定的延时时间后,执行任务方法。
周期性执行任务方法。
public static void main(String[] args) {
//设置核心池大小
int corePoolSize = 5;
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize);
ThreadTask threadTask = new ThreadTask();
// 延迟3秒,然后没2秒 周期性执行任务
scheduledThreadPoolExecutor.scheduleAtFixedRate(threadTask, 3, 2, TimeUnit.SECONDS);
}
public static class ThreadTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "==正在执行task ");
}
}
参考文章:
ThreadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和区别
—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。