提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
Java线程池详解
线程池
相比于进程,线程的创建成本比较低,但也需进行空间的初始化等工作。线程池则提供了线程回收利用的途径,减少了线程创建、销毁带来的性能损失。
线程池,顾名思义就是一个存放线程的池子,当需要线程时可以从线程池中取出线程,当不需要线程时也不需要销毁线程,只需要将程还入线程池即可。线程池带来了以下的优点:
- 通过线程的给出与收回实现了线程的重复利用,从而减少了线程频繁创建与销毁带来的性能损耗。
- 获得线程不需要创建,而只需要从线程池取出。这提升了获取线程的速度。
一、线程池类图
Executor是一个顶层的线程池接口,ExecutorService接口则在Executor的基础上定义了更多的方法比如:提交任务,执行任务,关闭线程池等。最终的线程池都是ExecutorService的实现类,我们可以用ExecutorService中的方法控制线程池。
在这里必须要介绍的类是Executors类,这是一个工厂类,该类中提供了创建多种JDK自带的线程池的静态方法,我们会在后面详细介绍其中的几种常用线程池。
二、ThreadPoolExecutor类
为什么在这里对ThreadPoolExecutor类进行介绍?我们在实际应用场景中,面对不同的业务需求,需要创建具有不同功能的线程池,例如,定时任务的线程池,固定大小的线程池,弹性大小的线程池等。这时候就需要我们对线程池的参数进行一些设置。Executor的子类中,非抽象类只有ScheduledThreadPoolExecutor类(定时任务),ThreadPoolExecutor类,ForkJoinPool类。我们以ThreadPoolExecutor为例子,介绍一些创建线程池需要设置的参数。
ThreadPoolExecutor的构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这里我们给出参数源码解释
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the {@link Executors} factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
*/
- corePoolSize:核心线程数,表示线程池支持的最小线程数;
- maximumPoolSize 最大线程数,当线程数大于核心线程数后,并且阻塞队列里存放满了等待执行的任务,才会创建一个“临时”新线程从阻塞队列头部取出任务,线程池还能接受maximumPoolSize - corePoolSize个“临时”新线程;
- keepAliveTime: 保持存活时间,空闲线程的存活时间,为了更好的复用线程;
- unit: 线程存活时间的单位;
- workQueue: 阻塞队列,等待线程的存放任务的队列;
- threadFactory:创建线程的工厂类,通常我们会自定义一个threadFactory设置线程的名称,这样我们就可以知道线程是由哪个工厂类创建的,可以快速定位;
- handler:线程池执行拒绝策略,当线数量达到maximumPoolSize大小,并且workQueue也已经塞满了任务的情况下,线程池会调用handler拒绝策略来处理请求。
假设:线程池corePoolSize=5,线程池初始化时不会自动创建线程,当有4个任务同时进来时,执行execute方法会新建【4】条线程来执行任务;
前面的4个任务都没完成,现在又进来2个任务,会新建【1】条线程来执行任务,这时poolSize=corePoolSize,还剩下1个任务,线程池会将剩下这个任务塞进阻塞队列(workQueue)中,等待空闲线程执行;
如果前面6个任务还是没有处理完,这时又同时进来了5个任务,此时还没有空闲线程来执行新来的任务,所以线程池继续将这5个任务塞进阻塞队列,但发现阻塞队列已经满了,核心线程也用完了,还剩下1个任务不知道如何是好,于是线程池只能创建【1】条“临时”线程来执行这个任务了;
这里创建的线程用“临时”来描述还是因为它们不会长期存在于线程池,它们的存活时间为keepAliveTime,并且poolSize<maximumPoolSize。此后线程池会维持最少corePoolSize数量的线程。
三、常见线程池的创建
1. newFixedThreadPool
它是一种固定大小的线程池;corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。
代码如下(示例):
public class ThreadPoolTests {
//使用日志打印线程信息
private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
//普通线程池,线程池内固定拥有5个线程
ExecutorService executorService = Executors.newFixedThreadPool(5);
private void sleep (long m){
try {
Thread.sleep(m);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testExecutorService(){
Runnable task = new Runnable() {
@Override
public void run() {
logger.info("Hello ExecutorService");
}
};
for (int i = 0; i < 10; i++){
executorService.submit(task);
}
//sleep(10000);
}
}
日志信息打印结果:
2021-07-26 16:55:55,535 INFO [pool-1-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-4] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,536 INFO [pool-1-thread-5] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-5] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-4] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
2021-07-26 16:55:55,537 INFO [pool-1-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:58] Hello ExecutorService
我们可以观察到线程池pool-1中的1-5个线程交替输出“Hello ExecutorService”。
2. newScheduledThreadPool
代码如下(示例):
public class ThreadPoolTests {
//使用日志打印线程信息
private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
// JDK可定时执行任务的线程池
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
private void sleep (long m){
try {
Thread.sleep(m);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//执行定时任务的线程池
@Test
public void testScheduledExecutor(){
Runnable task = new Runnable() {
@Override
public void run() {
logger.info("Hello ExecutorService");
}
};
//param:任务,第一次执行任务的初始延迟,任务周期间隔,初始延迟和周期参数的时间单位
scheduledExecutorService.scheduleAtFixedRate(task, 10000, 1000, TimeUnit.MILLISECONDS);
sleep(30000);
}
}
日志信息打印结果:
2021-07-26 17:01:44,827 INFO [pool-2-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:45,829 INFO [pool-2-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:46,831 INFO [pool-2-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:47,837 INFO [pool-2-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:48,836 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:49,838 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:50,827 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:51,832 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:52,838 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:53,826 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:54,832 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:55,835 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:56,838 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:57,825 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:58,825 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:01:59,832 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:00,836 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:01,825 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:02,830 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:03,839 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
2021-07-26 17:02:04,830 INFO [pool-2-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:74] Hello ExecutorService
3. CachedThreadPool
它是一个可以无限扩大的线程池;它比较适合处理执行时间比较小的任务;corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程
代码如下(示例):
public class ThreadPoolTests {
//使用日志打印线程信息
private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
// JDK可定时执行任务的线程池
private ExecutorService CacheExecutorService = Executors.newCachedThreadPool();
//执行cache线程池
@Test
public void testCacheExecutorService(){
Runnable task = new Runnable() {
@Override
public void run() {
logger.info("Hello ExecutorService");
}
};
for (int i = 0; i < 10; i++){
CacheExecutorService.submit(task);
}
}
}
运行结果:
2021-07-26 19:01:51,856 INFO [pool-3-thread-2] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,857 INFO [pool-3-thread-4] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,856 INFO [pool-3-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,857 INFO [pool-3-thread-5] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,858 INFO [pool-3-thread-7] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,858 INFO [pool-3-thread-6] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,857 INFO [pool-3-thread-3] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,858 INFO [pool-3-thread-8] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,859 INFO [pool-3-thread-9] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
2021-07-26 19:01:51,859 INFO [pool-3-thread-10] c.n.c.ThreadPoolTests [ThreadPoolTests.java:87] Hello ExecutorService
我们可以看到每提交一个任务,都会有一条新新城产生。
4. SingleThreadExecutor
它只会创建一条工作线程处理任务;采用的阻塞队列为LinkedBlockingQueue;
代码如下(示例):
public class ThreadPoolTests {
//使用日志打印线程信息
private static Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class);
// JDK可定时执行任务的线程池
private ExecutorService SingleExecutorService = Executors.newSingleThreadExecutor();
@Test
public void testSingleExecutorService(){
Runnable task = new Runnable() {
@Override
public void run() {
logger.info("Hello ExecutorService");
}
};
for (int i = 0; i < 10; i++){
SingleExecutorService.submit(task);
}
}
}
运行结果:
2021-07-26 19:07:10,887 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,889 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
2021-07-26 19:07:10,890 INFO [pool-4-thread-1] c.n.c.ThreadPoolTests [ThreadPoolTests.java:104] Hello ExecutorService
每次都是同一条线程在执行任务。