第3章 线程池
1.3 线程池
1.3.1 线程池是什么
手动创建线程的问题
前边我们学习了创建线程的方法,创建一个Thread对象,调用它的start()方法即启动线程,此方法虽简单但是如果在生产中遇到一个任务就创建一个线程去执行则存在问题:
1、创建一个线程也需要耗费系统资源,如果无限制的创建线程会耗尽资源,最终使整个进程崩溃。
2、遇到一个任务总是新创建一个线程去执行任务,这种方式效率不高,对于高并发且要求快速响应的场景无法满足需求。
线程池的好处
线程池解决了手动创建线程的问题,线程池顾名思义是存放线程的池子,数据库连接池,网络连接池等它们的思想都是类似的。线程池实现了对线程的统一管理,下边是线程池的结构图:

从上图可以看出线程池包括如下组件:
1、执行接口 外界使用线程池的接口,比如:提交任务接口,停止线程池接口等。
2、线程池 存放线程的集合,已创建的线程对象会放在集合中,集合中的线程会重复被利用。 当提交一个任务到线程池中,如果有空闲的线程(即执行完任务等待新任务的线程)则用空闲线程去执行任务,否则创建新的线程去执行任务。
3、任务队列 当线程池中的核心线程个数达到最大限制时,此时将任务暂时放在任务队列中等待线程从任务队列获取任务并执行。 在生产中,一般任务队列的容量是有限制的,当任务队列已满且没有新的线程去执行任务,此时提交任务给线程池会被拒绝。
4、线程工厂 线程工厂是创建线程的地方,Java面向对象教程中讲过工厂模式,这里可以自定义线程的工厂,定义线程创建的过程,稍后会专门讲解线程工厂。
总结线程池的优点如下:
1、对线程的创建、销毁进行统一管理和监控,控制资源的消耗。
2、节约资源,线程池中的线程可以重复利用,不用每次执行任务都创建新的线程。
3、提高效率,当有新任务到来时如果有空闲线程则直接去执行,不用先创建线程再执行任务。
线程池的初步使用
线程池主要包括两个集合用于存放线程和任务,如下图:

初学时可以先把线程池当成一个黑盒子,先从使用角度去学习它,学会用了再去学习内部细节。
首先要知道,没用线程池时我们需要自己手动创建线程,现在外界只管调用线程池的接口去提交任务即可,创建线程和执行任务都交给了线程池,如下图:

Java提供如下方式使用线程池:
1、Executors工具类
Executors工具类提供了很多方便使用线程池的方法,在测试程序时建议使用此工具类。
2、ThreadPoolExecutor类
使用ThreadPoolExecutor类可以配置详细工作参数,在生产中建议使用它。
初次学习线程池下边使用Executors工具类创建一个线程池进行测试。
Executors下边有很多方法,下边使用它的newCachedThreadPool() 方法创建一个线程池,其它工具方法稍后会详细讲解。
查询API文档有两个方法:
static ExecutorService newCachedThreadPool()
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
static newCachedThreadPool(ThreadFactory threadFactory)
稍后讲解两个方法都是静态方法,返回的ExecutorService对象是线程池的对外服务接口。
下边先用newCachedThreadPool() 方法测试,另一个方法稍后讲解。
1、创建一个线程池使用下边的代码:
ExecutorService threadPool = Executors.newCachedThreadPool();2、提交一个任务使用ExecutorService的execute方法,此方法接收一个任务对象,接口定义如下:
void execute(java.lang.Runnable command)
在将来的某个时间执行给定的命令。下边连续提交三个任务,观察输出日志的信息。
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Executors测试
* @author 预见猿份(yjoffer.com)
*
*/
public class ExecutorsTest {
private static Logger logger = Logger.getLogger(ExecutorsTest.class);
//测试newCachedThreadPool
public static void test_threadpool_first() {
// 创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
// 提交任务,任务对象使用Lambda表达式
threadPool.execute(() -> {
logger.debug( "执行任务...");
});
}
}
public static void main(String[] args) {
test_threadpool_first();
}
}输出:
21:59:08.175 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...
21:59:08.175 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务...
21:59:08.175 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务...从输出可以看出有三个不同的线程在执行任务。
线程重复利用测试
线程池应该对线程重利用才对,为什么上边用了三个不同的线程去执行任务?
在执行第一个任务时创建一个新线程执行任务,新线程还没有执行完成时又提交了一个新任务,此时则新创建了一个新线程去执行任务。
我们在提交一个任务后让主线程延迟1秒,1秒后再提交第二个任务,依此类推,测试线程能否重利用。
//测试线程重复利用
public static void test_threadpool_first2() {
// 创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
// 提交任务
threadPool.execute(() -> {
logger.debug( "执行任务...");
});
try {
//主线程休眠1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}输出:
22:06:22.457 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...
22:06:23.457 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...
22:06:24.457 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...TimeUnit.SECONDS.sleep(1);表示主线程休眠1秒,1秒后提交第2个任务,此时由于线程1已经完成任务并且空闲,所以线程1继续执行第2个,同理线程1执行了第3个任务。
1.3.2 线程池的工作流程
线程池的关键参数
线程池实现了对线程的统一管理,避免无限制去创建线程,这里有两个参数来控制线程池中的线程数。
1、corePoolSize
corePoolSize表示核心线程数,核心线程是永不终止的线程,核心线程完成了任务会继续等待新的任务。核心线程默认没有生存时间。
2、maximumPoolSize
maximumPoolSize表示最大线程数,即使核心线程创建满了线程池还会创建新线程,这些线程是为了辅助核心线程而存在,所以它们叫辅助线程,maximumPoolSize减去corePoolSize是辅助线程的数量。辅助线程默认有生存时间,当辅助线程空闲一定的时间后就会终止。
如何设置关键参数
如何设置核心线程数?
对于计算密集型任务建议为:核数+1,加1的目的是为了多一个替补线程。
对于IO密集型任务即CPU不能满负荷计算:核心*100%/CPU使用比例,例如一个8核CPU,计算这类任务只用50%的时间,套公式如下:
8*1/0.5=16如何设置辅助线程数?
辅助线程数等于maximumPoolSize - corePoolSize,核心线程数加辅助线程数不能大于maximumPoolSize。
建议辅助线程数是核心线程的0.5到1倍即可。
线程池的工作流程
线程池在什么情况下会去创建新线程呢?学习下边线程池的工作流程你将会知道一切。
下图是线程池的工作流程:

工作流程描述如下:
1、提交任务给线程池。
2、线程池判断是否达到核心线程数。
未达到:创建核心线程去执行任务。
达到:判断任务队列是否满
3、线程池判断任务队列是否满
未满:将任务加入任务队列,等待线程去执行任务。
已满:判断是否达到最大线程数
4、线程池判断是否达到最大线程数
未达到:创建辅助线程去执行任务。
达到:拒绝任务
1.3.3 ThreadPoolExecutor入门
ThreadPoolExecutor构造方法
下边查阅ThreadPoolExecutor类的API:
ThreadPoolExecutor有很多构造方法 ,其中下边的构造方法参数最完整。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)参数说明如下:
1、corePoolSize:核心线程数,表示含义请参考“线程池的工作流程章节”。
2、maximumPoolSize:最大线程数,表示含义请参考“线程池的工作流程章节”。
3、keepAliveTime:线程生存时间,默认情况只有辅助线程有生存时间,当设置allowCoreThreadTimeOut为true则核心线程也有生存时间。
对于密集型任务可将keepAliveTime设置为0,永不过期,一般情况可设置为60秒,具体情况再具体分析。
4、unit:keepAliveTime参数的时间单位。
5、workQueue : 任务队列, 暂时保存提交的任务。当核心线程全部在忙时对新提交的任务会暂存在队列中等待处理。
6、ThreadFactory:线程工厂,负责创建线程。
7、RejectedExecutionHandler: 当达到线程数限制和队列容量时,提交任务将被阻止,此时执行指定的RejectedExecutionHandler拒绝处理程序。
IllegalArgumentException - 如果以下某项成立则抛出异常:
corePoolSize < 0
keepAliveTime < 0
maximumPoolSize <= 0
maximumPoolSize < corePoolSize
NullPointerException - 如果 workQueue或 threadFactory或 handler为空
ThreadPoolExecutor入门程序
测试1:连续提交8个任务,且中间主线程休眠1秒,观察输出结果。
测试代码如下:
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.*;
/**
* Executors测试
* @author 预见猿份(yjoffer.com)
*
*/
public class ExecutorsTest {
private static Logger logger = Logger.getLogger(ExecutorsTest.class);
//测试ThreadPoolExecutor,只用核心线程
public static void test_threadPoolExecutor() {
// 使用ThreadPoolExecutor创建线程池
//核心线程数为2,最大线程数为5,超时单位为秒,任务队列为ArrayBlockingQueue(有界队列)且容量为3,使用默认线程工厂,阻止程序为AbortPolicy类型
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2 , 5 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(3) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
for (int i = 0; i < 8; i++) {
int n = i;
threadPoolExecutor.execute(() -> {
logger.debug( "执行任务"+n);
});
try {
//主线程休眠1秒(单位为毫秒)
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
test_threadPoolExecutor();
}
}输出:
11:34:01.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务0
11:34:02.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务1
11:34:03.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务2
11:34:04.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务3
11:34:05.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务4
11:34:06.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务5
11:34:07.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务6
11:34:08.125 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务7从输出可以看出是两个不同的线程执行8个任务,因为线程池设置的核心线程为2,由于主线程休眠1秒,核心线程完成任务后为空闲则继续执行新的任务。
测试2:测试辅助线程
下边把主线程中间的休眠去掉,当任务到来时核心线程还没有执行完成,此时则创建辅助线程执行任务,按照线程池定义可知线程个数最大为5,观察输出结果。
//测试ThreadPoolExecutor,使用核心线程、辅助线程及任务队列
public static void test_threadPoolExecutor2() {
// 使用ThreadPoolExecutor创建线程池
//核心线程数为2,最大线程数为5,超时单位为秒,任务队列为ArrayBlockingQueue(有界队列)且容量为3,使用默认线程工厂,阻止程序为AbortPolicy类型
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2 , 5 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(3) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
for (int i = 0; i < 8; i++) {
int n = i;
threadPoolExecutor.execute(() -> {
logger.debug( "执行任务"+n);
});
}
}输出:
11:35:32.177 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务5
11:35:32.177 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-5] - 执行任务7
11:35:32.177 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-4] - 执行任务6
11:35:32.177 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务1
11:35:32.177 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务0
11:35:32.200 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-4] - 执行任务4
11:35:32.200 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-5] - 执行任务3
11:35:32.200 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务2从输出可以看出是5个不同的线程执行8个任务,因为线程池设置的最大线程为5,任务队列容量为3,所以此线程池有能力连续接收8个任务。
测试3:任务拒绝
下边我们加大提交任务的 个数,更改为10,测试任务是否被拒绝:
因为任务完成后线程可以再利用,所以这里设置任务数稍微多一些。
//测试ThreadPoolExecutor,拒绝任务
public static void test_threadPoolExecutor3() {
// 使用ThreadPoolExecutor创建线程池
//核心线程数为2,最大线程数为5,超时单位为秒,任务队列为ArrayBlockingQueue(有界队列)且容量为3,使用默认线程工厂,阻止程序为AbortPolicy类型
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2 , 5 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(3) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
for (int i = 0; i < 10; i++) {
int n = i;
threadPoolExecutor.execute(() -> {
logger.debug( "执行任务"+n);
});
}
}输出:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.yjoffer.javase.thread.basic.ExecutorsTest$$Lambda$1/558638686@568db2f2 rejected from java.util.concurrent.ThreadPoolExecutor@378bf509[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.yjoffer.javase.thread.basic.ExecutorsTest.test_threadPoolExecutor3(ExecutorsTest.java:122)
at com.yjoffer.javase.thread.basic.ExecutorsTest.main(ExecutorsTest.java:128)
11:36:42.974 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务1
11:36:42.974 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-5] - 执行任务7
11:36:42.974 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-4] - 执行任务6
11:36:42.974 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务0
11:36:42.974 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务5
11:36:42.995 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-4] - 执行任务4
11:36:42.995 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-5] - 执行任务3
11:36:42.995 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务2从输出结果可以看出线程池无法处理新任务,任务被拒绝,抛出了RejectedExecutionException异常。
1.3.4 阻塞与非阻塞
什么是阻塞与非阻塞
ThreadPoolExecutor类型使用了BlockingQueue阻塞队列作为任务队列。
什么是阻塞与非阻塞?
阻塞与非阻塞是指执行程序时等待结果的状态。
阻塞是指执行程序不能立即返回结果 ,长期等待当前线程或等待一定的时间。
非阻塞是指执行程序可以立即返回结果。
举个生活的例子,你去餐厅就餐,由于没有座位需要排队等待,这里排队等待就是阻塞,有空位了就解除阻塞开始就餐。你在就餐前先打电话预约,服务员会告诉你是否位置,电话预约就是非阻塞操作,不管是否有位置都会立即得到回复。
拿队列来举例子,LinkedList是非阻塞队列,比如:从队列获取一个元素,无论是否可以获取到元素都会立即返回一个结果。BlockingQueue是阻塞队列的接口,阻塞队列的有些操作不会立即返回结果,比如:从阻塞队列获取一个元素,如果队列为空则线程会一直等待,直到获取到元素为止。
BlockingQueue介绍
BlockingQueue是阻塞队列的接口,队列是一种线性表,它在队尾添加元素,在队头删除元素,具有先进先出(First In First Out、FIFO)的特点。
下边通过BlockingQueue的API文档查阅BlockingQueue的常用方法,BlockingQueue继承Queue接口、Collection接口,下边的方法中有一些方法是阻塞方法:
boolean add(E e)
将指定的元素插入到此队列中,如果可以立即执行此操作而不违反容量限制,在成功后返回 true。 如果当前没有可用空间,则抛出IllegalStateException。
boolean contains(Object o)
如果此队列包含指定的元素则返回 true
void put(E e)
将指定的元素插入到此队列中,如果队列已满则等待,待队列有空闲再插入。
boolean offer(E e)
将指定的元素插入到此队列中,如果可以立即执行此操作而不会违反容量限制在成功时返回true ,如果当前没有可用空间则返回false。
boolean offer(E e, long timeout, TimeUnit unit)
将指定的元素插入到此队列中,等待指定的等待时间(如有必要)才能使空间变得可用。
E poll(long timeout, TimeUnit unit)
检索并删除此队列的头,等待指定的等待时间(如有必要)使元素变为可用。
E take()
检索并删除此队列的头,队列为空则等待有可用元素。
boolean remove(Object o)
从该队列中删除指定元素的单个实例(如果存在)。并不是所有方法具有阻塞特点,下边是阻塞方法 :
void put(E e)
将指定的元素插入到此队列中,如果队列已满则等待,待队列有空闲再插入。
boolean offer(E e, long timeout, TimeUnit unit)
将指定的元素插入到此队列中,等待指定的等待时间(如有必要)才能使空间变得可用。
E poll(long timeout, TimeUnit unit)
检索并删除此队列的头,等待指定的等待时间(如有必要)使元素变为可用。
E take()
检索并删除此队列的头,队列为空则等待有可用元素。BlockingQueue的常用实现类如下:

1、ArrayBlockingQueue,有边界的阻塞队列,内部使用数组实现。
2、DelayQueue,无界阻塞队列,指定每个元素的到期时长,元素在延迟到期时才被使用。
3、LinkedBlockingQueue,无界阻塞队列(也可有界),内部使用链表实现。。
4、PriorityBlockingQueue,无界阻塞队列,指定每个元素的优先级,按优先级排序。
5、SynchronousQueue,具有一个元素的阻塞队列,每个插入操作必须等待另一个线程相应的删除操作,反之亦然。测试阻塞与非阻塞的区别
下边测试阻塞与非阻塞的区别 :
poll()方法是非阻塞方法,检索并删除此队列的头,如果此队列为空,则返回 null ,调用poll()可以立即返回结果。
take()方法是阻塞方法,检索并删除此队列的头,队列为空则阻塞等待,待队列中有元素后返回。
测试代码如下:
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.*;
/**
* BlockingQueue测试
* @author 预见猿份(yjoffer.com)
*/
public class ExecutorsTest {
private static Logger logger = Logger.getLogger(ExecutorsTest.class);
//非阻塞与阻塞的区别
public static void test_nonblockingAndBlocking(){
//定义一个阻塞队列
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
//非阻塞操作
String v1 = arrayBlockingQueue.poll();
logger.debug("非阻塞方法调用立即返回结果"+v1);
String v2 = null;
try {
//take()为阻塞方法,没有元素则阻塞
logger.debug("由于队列没有元素,take()开始阻塞...");
v2 = arrayBlockingQueue.take();
logger.debug("阻塞方法等待后返回结果"+v2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
test_nonblockingAndBlocking();
}
}运行程序如下:
11:24:13.358 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[main] - 非阻塞方法调用立即返回结果null
11:24:13.382 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[main] - 由于队列没有元素,take()开始阻塞...单独启动一个线程向队列放入元素,查看效果:
//非阻塞与阻塞的区别,在单独一个线程向队列放入元素
public static void test_nonblockingAndBlocking2(){
//定义一个阻塞队列
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
//单独启动一个线程向队列放元素
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
arrayBlockingQueue.put("www.yjoffer.com");
logger.debug("向队列写入元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//非阻塞操作
String v1 = arrayBlockingQueue.poll();
logger.debug("非阻塞方法调用立即返回结果"+v1);
String v2 = null;
try {
//take()为阻塞方法,没有元素则阻塞
logger.debug("由于队列没有元素,take()开始阻塞...");
v2 = arrayBlockingQueue.take();
logger.debug("阻塞方法等待后返回结果"+v2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}运行程序,输出 :
11:25:07.733 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[main] - 非阻塞方法调用立即返回结果null
11:25:07.757 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[main] - 由于队列没有元素,take()开始阻塞...3秒后输出 :
11:25:07.733 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[main] - 非阻塞方法调用立即返回结果null
11:25:07.757 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[main] - 由于队列没有元素,take()开始阻塞...
11:25:10.733 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - 向队列写入元素
11:25:10.733 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[main] - 阻塞方法等待后返回结果www.yjoffer.com阻塞现象断点调试
ArrayBlockingQueue队列的put(E e)和take()方法都是阻塞方法。
put(E e): 向队列插入数据,当队列已满则等待,待队列有位置解除阻塞。
take(): 从队列取数据,当队列为空则等待,待队列有数据解除阻塞。
在上边代码的put方法和 take方法处打上断点,如下图:

设计debug多线程调试,右键点击两个断点,选择“Thread”模式

然后debug运行:
当达到断点处时main线程和thread-1线程都处于运行状态,如下图:

切换到main线程,向上执行一步,main线程开始阻塞

阻塞的原因是因为队列中没有元素。
此时将Thread-1线程向下执行一步,向队列放入元素,按照预期main线程应该解除阻塞

通过测试理解了阻塞操作的现象,请学习者参考教学视频多多测试去理解。
1.3.5 Executors工具类介绍
Executors工具类介绍
Executors工具类提供了很多简便的方法创建线程池,每个方法都是静态方法,都返回ExecutorService对象,ExecutorService是线程池的对外服务接口,如下表所示:
| 方法名 | 描述 |
|---|---|
| newCachedThreadPool | 创建一个可缓存的线程池,线程数量可无限增长,线程空闲60秒则被回收。 |
| newFixedThreadPool | 创建一个固定大小的线程池,线程数量固定。 |
| newSingleThreadExecutor | 创建一个单线程的线程池,线程数量为1。 |
| newScheduledThreadPool | 创建一个定时调度的线程池,线程数量可无限增长。 |
通过上表可知Executors工具类的方法分为四类:
1、newCachedThreadPool
查看newCachedThreadPool() 方法的源代码如下:


newCachedThreadPool() 方法不可以指定线程个数,通过源代码看出,通过newCachedThreadPool() 创建的核心线程数为0,最大线程数为Integer.MAX_VALUE,线程生存时间为60秒,任务队列为SynchronousQueue。
弊端:由于最大线程数设置为Integer.MAX_VALUE,不设置核心线程,请求则创建线程,当请求过多时会创建大量的线程,最后内存溢出。
2、newFixedThreadPool
查看newFixedThreadPool(int nThreads)方法的源代码:


newFixedThreadPool可以指定线程个数,从源码上可以看出newFixedThreadPool将线程个数设置为corePoolSize和maximumPoolSize都一致,并且线程的生存期为0,这说明所创建的线程都为核心线程,除非允许核心线程有生存期,否则它将永远活下去。newFixedThreadPool所使用的任务队列是LinkedBlockingQueue。
弊端:由于任务队列使用无界队列,当请求过多时任务处理速度跟不上任务的提交速度,将出现任务大量积压,最后内存溢出。
3、newSingleThreadExecutor
newSingleThreadExecutor方法创建一个单线程的线程池,相当于newFixedThreadPool(1)的实现,查看源代码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}方法不可以指定线程个数,通过源代码看出newSingleThreadExecutor创建的核心线程数为1,最大线程数为1,线程的生存期为0,这说明所创建的线程都为核心线程。
弊端:同newFixedThreadPool。
4、newScheduledThreadPool
newScheduledThreadPool方法创建一个可以定时调度的线程池,除了newScheduledThreadPool以外其它三种方法都是不同层度对ThreadPoolExecutor的使用,newScheduledThreadPool方法则使用了ScheduledThreadPoolExecutor类, 查看源代码如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}ScheduledThreadPoolExecutor类继承了 ThreadPoolExecutor类并实现了定时调度执行任务。
弊端:最大线程为Integer.MAX_VALUE,当请求过多时会创建大量的线程,最后内存溢出,它的弊端同newCachedThreadPool。
基于上边的弊端建议Executors工具类不要在生产中使用,可用在测试程序中。
Executors工具类测试
了解了每种方法的区别下边进行简单的测试,由于newScheduledThreadPool方法与其它三类方法的不同在后边讲解“任务调度”知识时单独测试,其它三方法中这里只测试一种即可 。
下边测试newFixedThreadPool方法。
创建线程池,指定线程个数为2,连续提交3个任务。
//测试newFixedThreadPool
public static void test_fixedThreadPool() {
// 创建线程池,指定线程个数为2
ExecutorService threadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 3; i++) {
// 提交任务
threadPool.execute(() -> {
logger.debug( "执行任务...");
});
}
}输出:
22:22:25.1 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...
22:22:25.1 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务...
22:22:25.26 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...从输出可以看出是由两个不同的线程执行3个任务。
测试单一线程池:
//测试newSingleThreadPool
public static void test_singleThreadPool() {
// 创建Single线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++) {
// 提交任务
threadPool.execute(() -> {
logger.debug( "执行任务...");
});
}
}输出 :
15:50:59.914 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...
15:50:59.942 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...
15:50:59.943 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务...从输出 可以看出只有线程池中只有一个线程在工作。
1.3.6 ThreadFactory线程工厂
ThreadFactory介绍
在学习面向对象开发时学习过工厂模式,工厂的作用是创建特定的对象。ThreadPoolExecutor线程池是使用工厂完成新线程的创建的。在ThreadPoolExecutor的构造方法参数中可以看到ThreadFactory线程工厂,线程池通过它来创建线程对象,它是一个接口,查看API如下:
public interface ThreadFactory {
Thread newThread(Runnable r);
}ThreadFactory只有一个newThread(Runnable r)方法,接收Runnable任务对象,返回Thread线程对象,线程池将调用此方法创建线程对象。
自定义ThreadFactory线程工厂
在Executors工具类中提供defaultThreadFactory()方法得到一个线程工厂对象,如果没有特殊需求则在开发中可以使用此方法创建一个线程工厂对象,如果有特殊的需求则需要程序员自定义ThreadFactory线程工厂类型,比如设置线程的名称、设置为守护线程等参数。
下边通过自定义一个简单的ThreadFactory线程工厂学会它的自定义方法。
下边的线程工厂实现了创建守护线程,代码如下 :
//定义线程池,自定义线程工厂
ExecutorService executorService = Executors.newFixedThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设为守护线程
thread.setDaemon(true);
return thread;
}
});完整代码如下:
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.*;
/**
* Executors测试
* @author 预见猿份(yjoffer.com)
*
*/
public class ExecutorsTest {
private static Logger logger = Logger.getLogger(ExecutorsTest.class);
//测试线程工厂
public static void test_threadfactory(){
//定义线程池,自定义线程工厂
ExecutorService executorService = Executors.newFixedThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设为守护线程
thread.setDaemon(true);
return thread;
}
});
executorService.execute(()->{
while (true){
logger.debug("执行任务");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.debug("结束");
}
public static void main(String[] args) {
test_threadfactory();
}
}运行程序,输出:
22:05:29.701 FINE com.yjoffer.javase.thread.basic.BlockingQueueTest[Thread-1] - 执行任务
22:05:30.725 FINE com.yjoffer.javase.thread.basic.BlockingQueueTest[Thread-1] - 执行任务
22:05:31.725 FINE com.yjoffer.javase.thread.basic.BlockingQueueTest[Thread-1] - 执行任务
22:05:32.701 FINE com.yjoffer.javase.thread.basic.BlockingQueueTest[main] - 结束Thread-1是守护线程,当主线程结束后Thread-1也结束了。
线程工厂是线程池很重要的一部分,它承担了创建线程的任务,在生产中会有自定义线程工厂的需求,因为需要对创建的线程设置符合业务要求的参数,上例虽然只是设置了守护线程,通过这个简单的例子需要明白自定义线程工厂的意义。
1.3.5 RejectedExecutionHandler拒绝程序
RejectedExecutionHandler介绍
在shutdown()方法和执行者结束之间,提交任务给执行者,这个任务将被拒绝,通过实现RejectedExecutionHandler,在执行者中管理拒绝任务
RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。

1、AbortPolicy
当任务被拒绝添加,丢弃任务并抛出RejectedExecutionException异常。
2、DiscardPolicy
当任务被拒绝添加,以静默方式丢弃任务,不抛出异常,此任务最终也不会执行,此策略不推荐。
3、DiscardOldestPolicy
当任务被拒绝添加,丢弃未处理的最旧的任务,再把当前任务添加到队列。
4、CallerRunsPolicy
当任务被拒绝添加,直接由当前线程调用被拒绝任务的处理程序(run方法)。
AbortPolicy测试
使用此策略当任务被拒绝添加,丢弃任务并抛出RejectedExecutionException异常。
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* RejectedExecutionHandler测试
* @author 预见猿份(yjoffer.com)
*
*/
public class RejectedExecutionHandlerTest {
private static Logger logger = Logger.getLogger(RejectedExecutionHandlerTest.class);
//测试AbortPolicy
public static void test_AbortPolicy() {
//核心线程数量为1 , 最大线程池数量为4, 任务容器的容量为1 ,空闲线程的最大存在时间为10s
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 4 , 10 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
// 提交10个任务,而该线程池最多可以处理5个任务(队列放1个,最大4个线程并发处理),当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常
for(int x = 0 ; x < 10 ; x++) {
final int y = x;
threadPoolExecutor.execute(() -> {
logger.debug(Thread.currentThread().getName() + "---->> 执行了任务"+y);
});
}
}
public static void main(String[] args) {
test_AbortPolicy();
}
}输出:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest$$Lambda$1/558638686@6d03e736 rejected from java.util.concurrent.ThreadPoolExecutor@568db2f2[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest.test_AbortPolicy(RejectedExecutionHandlerTest.java:26)
at com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest.main(RejectedExecutionHandlerTest.java:81)
22:15:19.661 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-1] - pool-1-thread-1---->> 执行了任务0
22:15:19.661 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-4] - pool-1-thread-4---->> 执行了任务4
22:15:19.661 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-3] - pool-1-thread-3---->> 执行了任务3
22:15:19.661 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-2] - pool-1-thread-2---->> 执行了任务2
22:15:19.685 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-1] - pool-1-thread-1---->> 执行了任务1从输出可以看出拒绝新任务,在threadPoolExecutor.execute处抛出了异常。
DiscardPolicy测试
更改程序的拒绝策略为DiscardPolicy,以静默方式丢弃任务,不抛出异常。
//测试DiscardPolicy
public static void test_DiscardPolicy() {
//核心线程数量为1 , 最大线程池数量为4, 任务容器的容量为1 ,空闲线程的最大存在时间为10s
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 4 , 10 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;
// 提交10个任务,而该线程池最多可以处理5个任务(队列放1个,最大4个线程并发处理),当我们使用DiscardPolicy这个任务处理策略的时候,以静默方式丢弃任务,不抛出异常。
for(int x = 0 ; x < 10 ; x++) {
final int y = x;
threadPoolExecutor.execute(() -> {
logger.debug(Thread.currentThread().getName() + "---->> 执行了任务"+y);
});
}
}重新运行程序,输出如下:
22:16:35.561 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-1] - pool-1-thread-1---->> 执行了任务0
22:16:35.561 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-2] - pool-1-thread-2---->> 执行了任务2
22:16:35.561 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-3] - pool-1-thread-3---->> 执行了任务3
22:16:35.562 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-4] - pool-1-thread-4---->> 执行了任务4
22:16:35.584 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-2] - pool-1-thread-2---->> 执行了任务1从输出可以看出拒绝任务并没有抛出异常。
DiscardOldestPolicy测试
使用此策略当任务被拒绝添加,丢弃未处理的最旧的任务,再把当前任务添加到队列。
//测试DiscardOldestPolicy
public static void test_DiscardOldestPolicy() {
//核心线程数量为1 , 最大线程池数量为4, 任务容器的容量为1 ,空闲线程的最大存在时间为10s
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 4 , 10 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy()) ;
// 提交10个任务,而该线程池最多可以处理5个任务(队列放1个,最大4个线程并发处理),当我们使用DiscardOldestPolicy这个任务处理策略的时候,就会丢弃未处理的最旧的任务,再把当前任务添加到队列
for(int x = 0 ; x < 10 ; x++) {
final int y = x;
threadPoolExecutor.execute(() -> {
logger.debug(Thread.currentThread().getName() + "---->> 执行了任务"+y);
});
}
}输出:
22:17:16.28 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-4] - pool-1-thread-4---->> 执行了任务4
22:17:16.28 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-2] - pool-1-thread-2---->> 执行了任务2
22:17:16.28 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-3] - pool-1-thread-3---->> 执行了任务3
22:17:16.28 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-1] - pool-1-thread-1---->> 执行了任务0
22:17:16.52 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-4] - pool-1-thread-4---->> 执行了任务9从输出可以看出,任务1、任务5、任务6到任务8都被丢弃。
CallerRunsPolicy测试
使用此策略当任务被拒绝添加,直接由当前线程调用被拒绝任务的处理程序(run方法)。
//测试CallerRunsPolicy
public static void test_CallerRunsPolicy() {
//核心线程数量为1 , 最大线程池数量为4, 任务容器的容量为1 ,空闲线程的最大存在时间为10s
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 4 , 10 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy()) ;
// 提交10个任务,而该线程池最多可以处理5个任务(队列放1个,最大4个线程并发处理),当我们使用CallerRunsPolicy这个任务处理策略的时候,就会由当前线程调用被拒绝任务的处理程序
for(int x = 0 ; x < 10 ; x++) {
final int y = x;
threadPoolExecutor.execute(() -> {
logger.debug(Thread.currentThread().getName() + "---->> 执行了任务"+y);
});
}
}输出:
22:18:04.616 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[main] - main---->> 执行了任务5
22:18:04.616 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-1] - pool-1-thread-1---->> 执行了任务0
22:18:04.616 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-2] - pool-1-thread-2---->> 执行了任务2
22:18:04.616 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-3] - pool-1-thread-3---->> 执行了任务3
22:18:04.616 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-4] - pool-1-thread-4---->> 执行了任务4
22:18:04.641 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-1] - pool-1-thread-1---->> 执行了任务1
22:18:04.641 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[main] - main---->> 执行了任务7
22:18:04.641 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-2] - pool-1-thread-2---->> 执行了任务6
22:18:04.642 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[main] - main---->> 执行了任务9
22:18:04.642 FINE com.yjoffer.javase.thread.basic.RejectedExecutionHandlerTest[pool-1-thread-3] - pool-1-thread-3---->> 执行了任务8从输出可以看出任务5、任务7、任务9由主线程直接调用。
1.3.6 线程池监控
监控线程池运行情况
线程池就像一个黑盒,只需要调用提交任务、结束任务等接口就可以实现多线程并发执行任务的需求,非常方便。但是在生产中我们需要监控线程池的运行情况,例如:线程池当前未执行的任务数、活动的线程数、任务的执行时长等,这些信息有助我们对软件的运行进行监控,当出现软件故障方便进行调试。
如何获取这些监控信息呢?
通过查询ThreadPoolExecutor的API,通过以下方法可以获取监控信息:
int getActiveCount()
返回正在执行任务的线程的大概数量。
long getCompletedTaskCount()
返回完成执行的任务的大致总数。
int getCorePoolSize()
返回核心线程数。
int getLargestPoolSize()
返回在池中同时进行的最大线程数。
int getPoolSize()
返回池中当前的线程数。
long getTaskCount()
返回计划执行的任务的大概总数。测试代码如下:
下边程序中,专门启动一个线程负责监控线程池的运行情况,并输出日志。
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.*;
/**
* Executors测试
* @author 预见猿份(yjoffer.com)
*
*/
public class ExecutorsTest {
private static Logger logger = Logger.getLogger(ExecutorsTest.class);
//监控线程池的运行情况
public static void monitor(){
//定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
//监控线程池
new Thread(()->{
while(true){
logger.debug("**********************************");
logger.debug("--------------->正在执行任务的线程数"+threadPoolExecutor.getActiveCount());
logger.debug("--------------->核心线程数"+threadPoolExecutor.getCorePoolSize());
logger.debug("--------------->当前线程数"+threadPoolExecutor.getPoolSize());
logger.debug("--------------->同时最大线程数"+threadPoolExecutor.getLargestPoolSize());
logger.debug("--------------->任务总数"+threadPoolExecutor.getTaskCount());
logger.debug("--------------->完成任务数"+threadPoolExecutor.getCompletedTaskCount());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//提交20个任务
for (int i = 0; i < 20; i++) {
final int n = i;
// 提交任务
threadPoolExecutor.execute(() -> {
logger.debug("线程"+n +":执行任务...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
monitor();
}
}运行程序,注意观察:
1、正在执行任务的线程数:逐渐增加,等待任务完成后逐渐减少。
2、核心线程数:保持不变,为5。
3、当前线程数:逐渐增大,等待任务完成后逐渐减少。
4、同时最大线程数:最高为10。
5、任务总数:大概数,20左右。
6、完成任务数:大概数,20左右。
输出日志(部分)如下:
08:44:21.980 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - **********************************
08:44:21.985 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 线程0:执行任务...
08:44:22.9 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->正在执行任务的线程数1
08:44:22.9 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->核心线程数5
08:44:22.9 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->当前线程数1
08:44:22.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->同时最大线程数1
08:44:22.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务总数1
08:44:22.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->完成任务数0
08:44:22.981 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 线程1:执行任务...
08:44:23.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - **********************************
08:44:23.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->正在执行任务的线程数2
08:44:23.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->核心线程数5
08:44:23.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->当前线程数2
08:44:23.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->同时最大线程数2
08:44:23.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务总数2
08:44:23.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->完成任务数0
08:44:23.981 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 线程2:执行任务...
08:44:24.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - **********************************
08:44:24.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->正在执行任务的线程数3
08:44:24.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->核心线程数5
08:44:24.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->当前线程数3
08:44:24.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->同时最大线程数3
08:44:24.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务总数3
08:44:24.10 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->完成任务数0
08:44:24.982 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-4] - 线程3:执行任务...
...
08:45:03.70 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - **********************************
08:45:03.70 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->正在执行任务的线程数0
08:45:03.70 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->核心线程数5
08:45:03.71 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->当前线程数5
08:45:03.71 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->同时最大线程数10
08:45:03.71 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务总数19
08:45:03.71 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->完成任务数19监控任务的执行时间
在生产中除了监控线程池的这些基本信息,对任务的执行时间也需要监控,比如:任务执行的最大时间、平均时间、最小时间等。
在ThreadPoolExecutor中没有提供现成的监控任务时间的方法,通过重写ThreadPoolExecutor的beforeExecute(Thread t, Runnable r) 和afterExecute(Runnable r, Throwable t) 方法计算任务的执行时间。查看API如下:
protected void beforeExecute(Thread t, Runnable r)
在给定的线程中执行给定的Runnable之前调用方法。
protected void afterExecute(Runnable r, Throwable t)
完成指定Runnable的执行后调用方法。代码如下:
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.*;
/**
* Executors测试
* @author 预见猿份(yjoffer.com)
*
*/
public class ExecutorsTest {
private static Logger logger = Logger.getLogger(ExecutorsTest.class);
/**
* 自定义线程池用于统计任务的执行时间
*/
static class CustomThreadPool extends ThreadPoolExecutor{
static Map<Runnable,Long> tempMap = new HashMap();
static Map<Runnable,Long> timeMap = new HashMap();
public CustomThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
tempMap.put(r,System.currentTimeMillis());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
if(tempMap.get(r)!=null){
//开始时间
Long start = tempMap.get(r);
//结束时间
Long end = System.currentTimeMillis();
if(end>start){
//时长
long duration = (end-start)/1000;
timeMap.put(r,duration);
}
}
}
//取出平均时间
public double getAverage(){
if(timeMap.values().size()>0){
return timeMap.values().stream().mapToLong(x->x).average().getAsDouble();
}
return -1;
}
//取出最大时间
public long getMax(){
if(timeMap.values().size()>0){
return timeMap.values().stream().mapToLong(x->x).max().getAsLong();
}
return -1;
}
//取出最小时间
public long getMin(){
if(timeMap.values().size()>0){
return timeMap.values().stream().mapToLong(x->x).min().getAsLong();
}
return -1;
}
//任务总数
public long getCount(){
if(timeMap.values().size()>0){
return timeMap.values().stream().mapToLong(x->x).count();
}
return -1;
}
}
//监控线程池的运行情况
public static void customMonitor(){
//定义线程池
CustomThreadPool threadPoolExecutor = new CustomThreadPool(5, 10, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
//监控线程池
new Thread(()->{
while(true){
logger.debug("**********************************");
logger.debug("--------------->正在执行任务的线程数"+threadPoolExecutor.getActiveCount());
logger.debug("--------------->核心线程数"+threadPoolExecutor.getCorePoolSize());
logger.debug("--------------->当前线程数"+threadPoolExecutor.getPoolSize());
logger.debug("--------------->同时最大线程数"+threadPoolExecutor.getLargestPoolSize());
logger.debug("--------------->任务总数"+threadPoolExecutor.getTaskCount());
logger.debug("--------------->完成任务数"+threadPoolExecutor.getCompletedTaskCount());
logger.debug("--------------->任务执行平均时间"+threadPoolExecutor.getAverage());
logger.debug("--------------->任务执行最大时间"+threadPoolExecutor.getMax());
logger.debug("--------------->任务执行最小时间"+threadPoolExecutor.getMin());
logger.debug("--------------->统计任务总数"+threadPoolExecutor.getCount());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Random random = new Random();
for (int i = 0; i < 20; i++) {
final int n = i;
// 提交任务
threadPoolExecutor.execute(() -> {
logger.debug("线程"+n +":执行任务...");
try {
//休眠0到10之间随机时长
TimeUnit.SECONDS.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
customMonitor();
}
}运行程序,注意观察任务执行时间统计:
1、任务平均时间
2、任务最大时间
3、任务最小时间
输出内容(部分)如下:
...
09:26:13.892 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 线程19:执行任务...
09:26:13.989 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - **********************************
09:26:13.989 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->正在执行任务的线程数4
09:26:13.989 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->核心线程数5
09:26:13.990 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->当前线程数7
09:26:13.990 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->同时最大线程数8
09:26:13.990 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务总数20
09:26:13.990 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->完成任务数16
09:26:13.991 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行平均时间5.4
09:26:13.991 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行最大时间9
09:26:13.991 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行最小时间2
09:26:13.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->统计任务总数15
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - **********************************
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->正在执行任务的线程数3
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->核心线程数5
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->当前线程数7
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->同时最大线程数8
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务总数20
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->完成任务数17
09:26:14.992 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行平均时间5.125
09:26:14.993 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行最大时间9
09:26:14.993 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行最小时间1
09:26:14.993 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->统计任务总数16
09:26:15.993 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - **********************************
09:26:15.993 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->正在执行任务的线程数3
09:26:15.994 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->核心线程数5
09:26:15.994 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->当前线程数7
09:26:15.994 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->同时最大线程数8
09:26:15.995 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务总数20
09:26:15.995 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->完成任务数17
09:26:15.996 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行平均时间5.125
09:26:15.996 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行最大时间9
09:26:15.996 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->任务执行最小时间1
09:26:15.997 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[Thread-1] - --------------->统计任务总数16
...1.3.7 关闭线程池
线程池状态介绍
线程池从启动到关闭经历如下状态:
RUNNING:运行中,接受新任务并处理排队的任务。
SHUTDOWN:关闭,不接受新任务,处理队列中的任务。
STOP:停止,不接受新任务,也不处理队列中的任务,中断正在运行的任务。
TIDYING:整理中,所以任务已终止,当前正在运行的工作线程为0,并调用terminated()方法。
TERMINATED: 终止,调用terminated()方法完成。
状态之间的转换如下:
RUNNING -> SHUTDOWN:调用 shutdown()方法由运行状态转为关闭。
(RUNNING or SHUTDOWN) -> STOP: 调用shutdownNow()方法由运行状态或关闭状态转为停止状态。
SHUTDOWN -> TIDYING:所以任务已终止,当前正在运行的工作线程为0,转为TIDYING状态。
STOP -> TIDYING: 线程池为空转为 TIDYING。
TIDYING -> TERMINATED: 转为 TIDYING后自动调用terminated()转为TERMINATED状态。
shutdown()与shutdownNow()
为了节省资源当线程池不再使用时要及时结束线程池,可以通过shutdown()和shutdownNow()两个方法结束线程池。
shutdown()方法:此方法会将线程池的状态设置为SHUTDOWN状态,不再接收新提交的任务,正在执行的任务会继续执行完成,队列中的任务继续执行完成。
shutdownNow()方法:此方法会将线程池的状态设置为STOP状态,不再接收新提交的任务,正在执行的任务立即停止,没有执行的任务不再执行。
shutdown()测试
下边这个程序使用线程池执行任务,共执行5个任务,其中有两个任务放在队列中,启动线程后立即执行shutdown()。
代码如下:
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.concurrent.*;
/**
* Executors测试
* @author 预见猿份(yjoffer.com)
*
*/
public class ExecutorsTest {
private static Logger logger = Logger.getLogger(ExecutorsTest.class);
//测试shutdown(),调用shutdown()后队列中的任务会执行完成
public static void test_shutdown(){
//核心线程2,辅助线程2,队列2,生存时长60秒
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2 , 4 , 60 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(2) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(()->{
logger.debug("执行任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
threadPoolExecutor.shutdown();
}
public static void main(String[] args) {
test_shutdown();
}
}输出如下:
21:13:58.815 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务
21:13:58.815 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务
21:13:58.815 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务
21:14:01.839 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务
21:14:01.839 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务从输出可以看出5个任务全部执行完毕。
如果在shutdown()方法后再提交任务线程池会拒绝任务,代码如下:
//测试shutdown(),调用shutdown()后提交任务会拒绝任务
public static void test_shutdown2(){
//核心线程2,辅助线程2,队列2,生存时长60秒
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2 , 4 , 60 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(2) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(()->{
logger.debug("执行任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
threadPoolExecutor.shutdown();
threadPoolExecutor.execute(()->{
logger.debug("执行任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}输出:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.yjoffer.javase.thread.basic.ExecutorsTest$$Lambda$2/1480010240@4dd8dc3 rejected from java.util.concurrent.ThreadPoolExecutor@6d03e736[Shutting down, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.yjoffer.javase.thread.basic.ExecutorsTest.test_shutdown2(ExecutorsTest.java:162)
at com.yjoffer.javase.thread.basic.ExecutorsTest.main(ExecutorsTest.java:183)
21:21:08.236 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务
21:21:08.236 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务
21:21:08.236 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务
21:21:11.263 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务
21:21:11.263 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务从输出可以看出第6个任务拒绝提交。
shutdownNow()测试
shutdownNow()与shutdown()的区别是shutdownNow()执行后会立即中断正在执行的任务并且任务队列中的任务不再执行。
测试代码如下:
//测试shutdownNow()
public static void test_shutdownNow(){
//核心线程2,辅助线程2,队列2,生存时长60秒
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2 , 4 , 60 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(2) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(()->{
logger.debug("执行任务");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
threadPoolExecutor.shutdownNow();
}输出:
21:44:21.189 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-3] - 执行任务
21:44:21.189 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-2] - 执行任务
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.yjoffer.javase.thread.basic.ExecutorsTest.lambda$test_shutdownNow$10(ExecutorsTest.java:180)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.yjoffer.javase.thread.basic.ExecutorsTest.lambda$test_shutdownNow$10(ExecutorsTest.java:180)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
21:44:21.189 FINE com.yjoffer.javase.thread.basic.ExecutorsTest[pool-1-thread-1] - 执行任务
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.yjoffer.javase.thread.basic.ExecutorsTest.lambda$test_shutdownNow$10(ExecutorsTest.java:180)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)从输出可以看出执行的三个任务被中断 ,并且任务队列中的两个任务不再执行。
