1 概述
-
一开始听到这么牛逼的名字,博主还以为是操作系统的底层直接提供的,可以用来直接创建线程的。看完源码后才知道,线程池实际上是java.util.concurrent包下面用来管理线程的工具,开发语言是java代码,启动线程还是依赖于底层提供的Thread类的Native方法start0()。
-
线程池的优点主要有 1)复用线程对象,降低频繁的创建和销毁线程是带来的开销。
2)方便管理线程。线程池已经封装了线程监管,异常处理,线程创建和销毁等方法,开箱即用。 3)提供并发处理性能。因为线程池中核心线程创建后,在不关闭的前提下,当下次有新的线程访问时,可以直接使用,不需要再重新创建。 -
线程池的处理流程如下:
2 自义线程池构造函数
自定义线程池是其他线程池框架的基础,根据不同workQueue会有不同的使用效果,workQueue都会对其他参数造成影响。因此在了解每个参数意义的前提下,我们需要根据场景去学习。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数含义:
corePoolSize : 核心线程数,一旦创建出来,在线程池空闲的时也不会被清除
maximumPoolSize : 池中允许的最大线程数
keepAliveTime :当任务全部处理完事,大于corePoolSize但是小于等于maximumPoolSize的那部分线程,会在keepAliveTime * unit 到达到后被清除
**unit ** :时间单位
workQueue : 阻塞队列,用于存储大于corePoolSsize的线程
threadFactory:线程工厂
handler:拒绝策略,当workQueue和maximumPoolSize都满了,对新进来的线程采用哪种处理策略
3 线程工程
线程工厂ThreadFactory并没有开启线程的作用,而是给线程池中开启的线程添加属性,比如设置线程命名,查看创建线程数,设置线程为守护线程,设置线程优先级等。ThreadFactory是一个接口
public interface ThreadFactory {
Thread newThread(Runnable r);
}
在自定义线程池的构造函数中采用以下实现方式
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
private static final AtomicInteger poolNumber = new AtomicInteger(1);
DefaultThreadFactory() {
//线程组
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
//定义线程的名字
//可以根据自己项目的要求定义线程的名字
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
4 拒绝策略
当线程池中的资源被用完时,线程池对新添加的的任务线程采用不同的处理策略。RejectedExecutionHandler是一个接口,在线程池中,其实现有4种。
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
//拒绝策略的接口,JDK自带或者自己根据业务实现的拒绝策略都必须实现这个接口
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
4.1 AbortPolicy
线程池默认的拒绝策略是AbortPolicy,直接抛出RejectExecutionException异常
public static class AbortPolicy implements RejectedExecutionHandler {
//无参构造函数
public AbortPolicy() { }
//直接抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
4.2 DiscardPolicy
将超出的任务丢弃,跟AbortPolicy很相似,只是不抛出异常
public static class DiscardPolicy implements RejectedExecutionHandler {
//无参构造函数
public DiscardPolicy() { }
//不排除异常,也没做任何处理,单纯丢掉
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
4.3 DiscardOldestPolicy
丢弃队列里最近一个任务,并执行当前任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
//无参构造函数
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//判断线程池是否关闭
if (!e.isShutdown()) {
//移除队列种的首个元素
e.getQueue().poll();
e.execute(r);
}
}
}
4.4 CallerRunsPolicy
使用线程池的调用者来执行超出的任务。比如本机测试中使用main方法调用线程池,那么就会使用main线程来执行超出的任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
//无参构造函数
public CallerRunsPolicy() { }
//使用线程池的调用者来执行超出的任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//判断线程池是否关闭
if (!e.isShutdown()) {
//run方法是使用当前线程执行
r.run();
}
}
}
5 线程池提交任务的方法
5.1 execute()
用于提交不需要返回值的任务
public void execute(Runnable command) {
..
{
5.2 submit()
submit方法底层执行实际上是调用execute方法,其主要用于提交需要返回值的任务,返回值的类型为Future,再通过阻塞的get()方法进行获取;也可以使用谷歌Guava包的ListenableFuture类进行异步获取。
public <T> Future<T> submit(Callable<T> task) {
//非空校验
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
//调用execute方法
execute(ftask);
//返回RunnableFuture对象
return ftask;
}
6 线程池的监控
6.1 getCorePoolSize()
获取线程池核心线程数。就是线程池初始化时以参数形式传进去的corePoolSize值。
private volatile int corePoolSize;
public int getCorePoolSize() {
return corePoolSize;
}
6.2 getPoolSize()
获取线程池中当前线程的数量
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private final HashSet<Worker> workers = new HashSet<Worker>();
public int getPoolSize() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//当线程池的状态为TIDYING或者TERMINATED时返回true
return runStateAtLeast(ctl.get(), TIDYING) ? 0
: workers.size();
} finally {
mainLock.unlock();
}
}
6.3 getQueue().size()
等待队列中的线程数
//等待队列,存储未处理的线程
private final BlockingQueue<Runnable> workQueue;
public BlockingQueue<Runnable> getQueue() {
return workQueue;
}
7 不同阻塞队列的影响
7.1 LinkedBlockingQueue
LinkedBlockingQueue的无参构造方式是无界的阻塞队列,默认大小是是Integer.MAX_VALUE。可以通过有参构造方法指定队列大小,从而成为有界的。
如果使用无界的阻塞队列,keepAliveTime和maximumPoolSize就变得没有意义。
因为超过corePoolSize的未执行任务会被放入无界队列中。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
7.2 ArrayBlockingQueue
ArrayListBlockingQueue是一个有界的等待队列,底层是基于数组实现,因此初始化的时候需要指定数组大小。
超过corePoolSize的未执行任务会被存放在ArrayListBlockingQueue中。
1)当ArrayListBlockingQueue中的数组已满,且多余的线程数+corePoolSize小于等于maximumPoolSize时,线程池会启动新的线程去处理。
public class ThreadPoolApp {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
2,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("线程名字:" +Thread.currentThread().getName());
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
for (int i = 0; i < 3; i++){
executor.execute(thread);
}
while(true){
System.out.println("PoolSize: " + executor.getPoolSize());
System.out.println("CorePoolSize: " + executor.getCorePoolSize());
System.out.println("QueueSize: " + executor.getQueue().size());
System.out.println("ActiveCount: " + executor.getActiveCount());
System.out.println("====================");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
控制台的打印结果:
PoolSize: 2
CorePoolSize: 1
QueueSize: 1
ActiveCount: 2
====================
线程名字:pool-1-thread-1
线程名字:pool-1-thread-2
线程名字:pool-1-thread-1
PoolSize: 1
CorePoolSize: 1
QueueSize: 0
ActiveCount: 0
2)当ArrayListBlockingQueue中的数组已满,且多余的线程数+corePoolSize大于maximumPoolSize时,线程池会抛出异常。
public class ThreadPoolApp {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
2,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("线程名字:" +Thread.currentThread().getName());
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
for (int i = 0; i < 4; i++){
executor.execute(thread);
}
while(true){
System.out.println("PoolSize: " + executor.getPoolSize());
System.out.println("CorePoolSize: " + executor.getCorePoolSize());
System.out.println("QueueSize: " + executor.getQueue().size());
System.out.println("ActiveCount: " + executor.getActiveCount());
System.out.println("====================");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
控制台的打印结果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-0,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Running, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at ThreadPool.ThreadPoolApp.main(ThreadPoolApp.java:89)
线程名字:pool-1-thread-2
线程名字:pool-1-thread-1
线程名字:pool-1-thread-2
7.3 SynchronousQueue
SynchronousQueue是一种无缓冲的等待队列,即不存储任何数据对象。因为每个插入操作put()方法必须等到另一个线程调用take()方法移除,否则插入操作一直处于阻塞状态,因此,并发性能最佳。
- 多余的线程数+corePoolSize小于等于maximumPoolSize时,线程池会启动新的线程去处理。
public class ThreadPoolApp {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
2,
0,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("线程名字:" +Thread.currentThread().getName());
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
for (int i = 0; i < 2; i++){
executor.execute(thread);
}
while(true){
System.out.println("PoolSize: " + executor.getPoolSize());
System.out.println("CorePoolSize: " + executor.getCorePoolSize());
System.out.println("QueueSize: " + executor.getQueue().size());
System.out.println("ActiveCount: " + executor.getActiveCount());
System.out.println("====================");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
控制台的打印结果:
PoolSize: 2
CorePoolSize: 1
QueueSize: 0
ActiveCount: 2
====================
线程名字:pool-1-thread-2
线程名字:pool-1-thread-1
PoolSize: 1
CorePoolSize: 1
QueueSize: 0
ActiveCount: 0
- 多余的线程数+corePoolSize大于maximumPoolSize时,线程池会抛出异常。
public class ThreadPoolApp {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
2,
0,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("线程名字:" +Thread.currentThread().getName());
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
for (int i = 0; i < 3; i++){
executor.execute(thread);
}
while(true){
System.out.println("PoolSize: " + executor.getPoolSize());
System.out.println("CorePoolSize: " + executor.getCorePoolSize());
System.out.println("QueueSize: " + executor.getQueue().size());
System.out.println("ActiveCount: " + executor.getActiveCount());
System.out.println("====================");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
控制台的打印结果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-0,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@135fbaa4[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at ThreadPool.ThreadPoolApp.main(ThreadPoolApp.java:89)
线程名字:pool-1-thread-1
线程名字:pool-1-thread-2
8 不同线程池条件的影响
8.1 小于等于corePoolSize
启动新的线程去处理,任务处理完后,空闲的线程不会被回收
8.2 大于corePoolSize且小于等于maximunmPoolSize
启动新的线程去处理,任务处理完后,空闲的线程不会被回收。
这里要注意,如果阻塞队列采用无界LinkedBlockingQueue,因为超过corePoolSize的线程会被存放到阻塞队列中,因此keepAliveTime参数对LinkedBlockingQueue不起作用。
8.3 大于maximunmPoolSize
当线程池中的资源被用完时,线程池对新添加的的任务线程采用不同的处理策略。
这里要注意,如果阻塞队列采用无界LinkedBlockingQueue,因为超过corePoolSize的线程会被存放到阻塞队列中,因此不需要使用拒绝策略。
9 线程池的关闭
9.1 shutdown
调用shutdown后,如果线程池中还有任务,会执行完剩下所有的任务才会停止。
如果新增线程到线程池,则会爆出RejectedExecutionException异常。
9.2 shutdownNow
调用shutdown后,如果线程池中还有任务,也会被中断,并抛出InterruptedException异常。
10 参考文献
1)JDK7在线文档
https://tool.oschina.net/apidocs/apidoc?api=jdk_7u4
2) JDK8在线文档
https://docs.oracle.com/javase/8/docs/api/
3) Bruce Eckel. Java编程思想,第4版,2007,机械工业出版社
4)方腾飞,魏鹏,程晓明. Java并发编程的艺术,第1版,2015年,机械工业出版社