Java高并发编程中ThreadPoolExecutor的使用及详细介绍-刘宇
作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。
一、什么是ThreadPoolExecutor?
ThreadPoolExecutor是一个ExecutorService,是一个线程池,通常使用 Executors 工厂方法配置。那么ThreadPoolExecutor与ExecutorService以及Executor接口的基本关系已经在前一篇博客讲解了:点击查看详情。关于Executors工厂方法的使用详情请看另一篇博客:点击查看详情
二、ThreadPoolExecutor的方法详解
由于ThreadPoolExecutor是继承了AbstractExecutorService抽象类,而AbstractExecutorService抽象类又实现了ExecutorService接口,所以有很多方法都是来自ExecutorService接口,这些方法就不做解释了,详情请看我前面一篇博客:点击查看详情
1、4种构造方法
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
2、构造方法中参数详解
2.1、corePoolSize
线程池的基本大小,线程池最小就有这么多线程。线程池刚创建时并不会立即就创建corePoolSize个线程,而是在等待有任务过来才会创建线程,而且是一个一个创建的,当所有任务执行完后core的线程也不会被回收。
2.2、maximumPoolSize
线程池中最大的线程数,当workQueue队列中的任务放不下的时候就会创建新线程,直至达到maximumPoolSize的值。无界的任务队列这个参数就没用了
2.3、keepAliveTime
线程最大空闲时间,如果在空闲时间内,任务队列没有饱和的话就会销毁除基本线程之外的线程。
2.4、unit
keepAliveTime的时间单位。可选的单位有Days、HOURS、MINUTES、MILLISECONDS、MICROSECONDS、NANOSECONDS。
2.5、workQueue
用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,按FIFO原则进行排序
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,吞吐量高于ArrayBlockingQueue。静态工厂方法Excutors.newFixedThreadPool()使用了这个队列
- SynchronousQueue: 一个不存储元素的阻塞队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。如果线程获取的元素时该队列不存在任何元素,则该线程会被阻塞,直至有元素插入。吞吐量高于LinkedBlockingQueue,静态工厂方法Excutors.newCachedThreadPool()使用了这个队列
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
2.6、threadFactory
线程创建工厂,通常可以用来给线程命名、查看创建线程数、给线程设置是否是后台运行、设置线程优先级等等
2.6、handler
拒绝策略。当队列中的元素放满并且线程池中的线程达到最大数量时,此时线程池处于饱和状态。此时就需要做出相应的策略应对,有如下四个选项:
AbortPolicy:默认策略,抛出异常
CallerRunsPolicy:使用调用者所在线程来运行该任务,不抛出异常
DiscardOldestPolicy:丢弃队列里最近的一个任务,然后再添加到队列中,不抛出异常
DiscardPolicy:直接忽略提交的任务
3、其他基本方法
3.1、execute方法
非阻塞方法。提交Runnable任务,没有返回值。
void execute(Runnable command)
3.2、getCorePoolSize方法
获取当前线程池基本线程的大小
int getCorePoolSize()
3.3、getMaximumPoolSize方法
获取当前线程池允许的最大线程数
int getMaximumPoolSize()
3.4、getQueue方法
获取任务队列,返回的是可修改的队列,我们可以直接添加任务。如果线程池内有空闲线程则会执行直接添加的任务,如果线程池是刚创建的core线程还没有创建则不会执行,因为直接添加的任务,线程池是收不到创建线程的信号的。
BlockingQueue<Runnable> getQueue()
3.5、getPoolSize方法
获取线程池当前的线程数
int getPoolSize()
3.6、getActiveCount方法
获取线程池当前处于活跃状态的线程数
int getActiveCount()
3.7、isTerminating方法
判断Executor是否正在关闭
boolean isTerminating()
3.8、allowCoreThreadTimeOut方法
设置允许将core线程在空闲的时候关闭,如果是用newFixedThreadPool创建的线程池,需要搭配setKeepAliveTime方法这种允许空闲的时间,因为newFixedThreadPool中的默认空闲时间为0。注意当core的线程数关闭为0时,线程池会关闭。
- value:是否开启
void allowCoreThreadTimeOut(boolean value)
3.9、setKeepAliveTime方法
设置允许线程的空闲时间
- time:时间
- unit:时间单位
void setKeepAliveTime(long time, TimeUnit unit)
3.10、remove方法
移除队列中还未执行的任务
- task:需要移除的任务
boolean remove(Runnable task)
3.11、prestartCoreThread方法
创建线程池后立即预创建一个Core线程,当有任务提交时会再创建一个新线程留作备用,直到线程数到达core线程数。返回是否创建成功。
boolean prestartCoreThread()
3.12、prestartAllCoreThreads方法
创建线程池后立即预创建所有Core线程,返回创建的线程数
int prestartAllCoreThreads()
三、ThreadPoolExecutor练习
1、简单练习
package com.brycen.concurrency03.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorExample {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue(1), r -> {
return new Thread(r);
}, new ThreadPoolExecutor.AbortPolicy());
threadPoolExecutor.submit(() -> sleepSeconds(10));
threadPoolExecutor.submit(() -> sleepSeconds(10));
threadPoolExecutor.submit(() -> sleepSeconds(10));
int corePoolSize = -1;
int maxPoolSize = -1;
int activeCount = -1;
int poolSize = -1;
int queueSize = -1;
while (true) {
int currentCorePoolSize = threadPoolExecutor.getCorePoolSize();
int currentMaxPoolSize = threadPoolExecutor.getMaximumPoolSize();
int currentActiveCount = threadPoolExecutor.getActiveCount();
int currentPoolSize = threadPoolExecutor.getPoolSize();
int currentQueueSize = threadPoolExecutor.getQueue().size();
if (corePoolSize != currentCorePoolSize || maxPoolSize != currentMaxPoolSize
|| activeCount != currentActiveCount || queueSize != currentQueueSize || poolSize != currentPoolSize) {
System.out.println("CorePoolSize: " + currentCorePoolSize);
System.out.println("MaximumPoolSize: " + currentMaxPoolSize);
System.out.println("ActiveCount: " + currentActiveCount);
System.out.println("PoolSize: " + currentPoolSize);
System.out.println("BlockingQueueSize: " + currentQueueSize);
corePoolSize = currentCorePoolSize;
maxPoolSize = currentMaxPoolSize;
activeCount = currentActiveCount;
queueSize = currentQueueSize;
poolSize = currentPoolSize;
System.out.println("==============================================");
TimeUnit.MILLISECONDS.sleep(100);
}
}
}
private static void sleepSeconds(int seconds) {
try {
System.out.println("** " + Thread.currentThread().getName() + " **");
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
** Thread-0 **
** Thread-1 **
CorePoolSize: 1
MaximumPoolSize: 2
ActiveCount: 2
PoolSize: 2
BlockingQueueSize: 1
==============================================
** Thread-1 **
CorePoolSize: 1
MaximumPoolSize: 2
ActiveCount: 1
PoolSize: 2
BlockingQueueSize: 0
==============================================
CorePoolSize: 1
MaximumPoolSize: 2
ActiveCount: 0
PoolSize: 2
BlockingQueueSize: 0
==============================================
CorePoolSize: 1
MaximumPoolSize: 2
ActiveCount: 0
PoolSize: 1
BlockingQueueSize: 0
==============================================
2、线程池关闭练习1
背景介绍:当我们的任务全部并行执行完关闭线程池后,如何串行执行我们后续代码。使用shutdown显然是不行的,因为他不是阻塞的,并行任务还没执行完我们的串行代码就被执行了。这时我们可以使用awaitTermination结合shutdown的方法,因为awaitTermination是阻塞的。
package com.brycen.concurrency03.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class ThreadPoolExecutorExample2 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue(10), r -> {
return new Thread(r);
}, new ThreadPoolExecutor.AbortPolicy());
//提交20个任务
IntStream.rangeClosed(1, 20).forEach(i->{
threadPoolExecutor.submit(() -> task(5,String.valueOf(i)));
});
threadPoolExecutor.shutdown();
//阻塞,等待线程池关闭
threadPoolExecutor.awaitTermination(1, TimeUnit.HOURS);
//下面可以进行串行执行代码
System.out.println("========all work over========");
}
private static void task(int seconds,String no) {
try {
System.out.println(Thread.currentThread().getName()+" ["+no+"] start work");
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
Thread-1 [2] start work
Thread-2 [3] start work
Thread-0 [1] start work
Thread-3 [4] start work
Thread-4 [5] start work
Thread-5 [6] start work
Thread-6 [7] start work
Thread-7 [8] start work
Thread-8 [9] start work
Thread-9 [10] start work
Thread-2 [11] start work
Thread-1 [13] start work
Thread-0 [12] start work
Thread-3 [14] start work
Thread-5 [16] start work
Thread-8 [18] start work
Thread-4 [15] start work
Thread-6 [17] start work
Thread-9 [20] start work
Thread-7 [19] start work
========all work over========
3、线程池关闭练习2
背景介绍:我们知道shutdownNow方法会立即关闭线程池,但是也会存在关闭不了的情况,因为shutdownNow中其实是对正在执行任务的线程进行interrupt打断,如果该任务中没有抛出interrupt异常的方法,则该线程就不会被打断,也就结束不了,这样的场景如:网络请求一个数据,这个数据非常庞大,耗时非常久,那么interrupt就不会使该线程中断,也就不会立即结束。
错误演示
- 虽然最后会显示未执行的任务数,但是线程池并未关闭。
package com.brycen.concurrency03.threadpool;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class ThreadPoolExecutorExample3 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue(10), r -> {
return new Thread(r);
}, new ThreadPoolExecutor.AbortPolicy());
// 提交20个任务
IntStream.rangeClosed(1, 20).forEach(i -> {
threadPoolExecutor.submit(() -> task(5, String.valueOf(i)));
});
List<Runnable> noRunTask = threadPoolExecutor.shutdownNow();
// 下面可以进行串行执行代码
System.out.println("未执行的任务数:" + noRunTask.size());
}
private static void task(int seconds, String no) {
System.out.println(Thread.currentThread().getName() + " [" + no + "] start work");
// 模拟网络请求
while (true) {
}
}
}
正确演示
- 解决方案就是利用ThreadFactory,将线程池创建的线程都设置为守护线程,这样当主线程挂掉之后,这样线程也就会跟着结束了
package com.brycen.concurrency03.threadpool;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class ThreadPoolExecutorExample3 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue(10), r -> {
Thread t = new Thread(r);
//设置为守护线程
t.setDaemon(true);
return t;
}, new ThreadPoolExecutor.AbortPolicy());
// 提交20个任务
IntStream.rangeClosed(1, 20).forEach(i -> {
threadPoolExecutor.submit(() -> task(5, String.valueOf(i)));
});
List<Runnable> noRunTask = threadPoolExecutor.shutdownNow();
// 下面可以进行串行执行代码
System.out.println("未执行的任务数:" + noRunTask.size());
}
private static void task(int seconds, String no) {
System.out.println(Thread.currentThread().getName() + " [" + no + "] start work");
// 模拟网络请求
while (true) {
}
}
}
运行结果:
- 成功关闭线程池,结束程序
Thread-0 [1] start work
Thread-2 [3] start work
Thread-4 [5] start work
Thread-1 [2] start work
Thread-3 [4] start work
Thread-5 [6] start work
Thread-6 [7] start work
Thread-7 [8] start work
Thread-8 [9] start work
Thread-9 [10] start work
未执行的任务数:10
4、关闭线程池中的core线程
大家都知道,线程池中的core线程是基本线程,默认是不会关闭的,只有当任务队列任务饱和时创建的额外线程才会在规定空闲时间之后关闭,那么我们如何让core线程也在规定空闲时间后关闭呢,下面看演示。
package com.brycen.part3.threadpool;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorExample2 {
public static void main(String[] args) throws InterruptedException {
//创建一个core和最大线程数都为5的线程池,且该线程池的空闲时间为0
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
//因为newFixedThreadPool默认创建的线程池的空闲时间为0,所以这边我们要设置空闲时间
threadPoolExecutor.setKeepAliveTime(5,TimeUnit.SECONDS);
//允许关闭core空闲线程
threadPoolExecutor.allowCoreThreadTimeOut(true);
//在没有接受到任务前,线程池内线程数为0
System.out.println(threadPoolExecutor.getPoolSize());
threadPoolExecutor.execute(() -> sleepSeconds(1));
threadPoolExecutor.execute(() -> sleepSeconds(1));
//休眠一下,确保任务被提交进入线程池
TimeUnit.MILLISECONDS.sleep(500);
//此时因为提交了两个任务,所以线程池启动了两个线程
System.out.println(threadPoolExecutor.getPoolSize());
//休眠一下,查看线程池中剩余线程数
TimeUnit.SECONDS.sleep(7);
System.out.println(threadPoolExecutor.getPoolSize());
}
private static void sleepSeconds(int seconds) {
try {
System.out.println("** " + Thread.currentThread().getName() + " **");
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果
- core线程数减为0时,线程池自动关闭
0
** Thread-0 **
** Thread-1 **
2
0
Process finished with exit code 0
5、提前创建线程池中的线程
在默认情况下,线程池刚创建的时候线程数是0,只有在接受到任务的时候才会去创建core线程,那么下面将演示如何让线程池提前创建core线程。
5.1、使用prestartCoreThread预创建一个线程
prestartCoreThread该方法会使线程池刚创建的时候就会预创建一个线程留作备用,当有一个任务提交后会随即再创建一个线程去备用,直至线程数达到core的线程数为止。
package com.brycen.part3.threadpool;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorExample3 {
public static void main(String[] args) throws InterruptedException {
//利用Executors工厂创建一个core线程和最大线程数都为5且空闲时间为0的线程池
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
//提前创建一个线程,如果有任务提交,那么会再创建一个线程,保证总有一个core线程是空闲的,最大为core线程数,达到core线程数后就不会在预创建了。
threadPoolExecutor.prestartCoreThread();
//这边输出1,因为我们预创建了一个线程
System.out.println(threadPoolExecutor.getPoolSize());
//提交一个任务
threadPoolExecutor.execute(() -> sleepSeconds(1));
TimeUnit.MILLISECONDS.sleep(500);
//此时core线程池中的线程数为2,因为我们总是预创建了一个线程
System.out.println(threadPoolExecutor.getPoolSize());
}
private static void sleepSeconds(int seconds) {
try {
System.out.println("** " + Thread.currentThread().getName() + " **");
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
1
** pool-1-thread-2 **
2
5.2、使用prestartAllCoreThreads预创建所有core线程
package com.brycen.part3.threadpool;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPoolExecutorExample4 {
public static void main(String[] args) throws InterruptedException {
//利用Executors工厂创建一个core线程和最大线程数都为5且空闲时间为0的线程池
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
//提前创建所有core线程
threadPoolExecutor.prestartAllCoreThreads();
//这边输出5,因为我们预创建了所有core线程
System.out.println(threadPoolExecutor.getPoolSize());
}
}
运行结果:
5