线程池的介绍
在Java开发中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程,而创建并销毁线程的过程势必会消耗内存。为了高效利用内存并提升性能,Java引入了线程池的概念。
线程池是一种基于池化技术的管理工具,它在应用程序中维护了一个线程集合。这些线程由线程池统一管理,可以根据需要被调配来执行任务。线程池的主要优势在于它可以方便地管理线程,减少内存消耗,并允许线程的复用。通过复用已存在的线程,线程池避免了频繁创建和销毁线程的操作,从而降低了系统的开销。这种设计使得线程池成为Java多线程编程中一种重要的资源管理和优化手段。
基于池化的例子有很多。例如,在处理数据库操作或Redis连接时,使用数据库连接池或Redis连接池,可以确保线程的高效利用,降低系统资源的占用。
线程池的配置
Java线程池的核心实现是java.util.concurrent.ThreadPoolExecutor类,它提供了丰富的配置选项来满足不同场景下的需求。在使用线程池时,合理配置这些选项是非常重要的,它可以帮助我们提高系统的性能和稳定性。下面是ThreadPoolExecutor的构造函数及其主要参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)核心线程数
corePoolSize:核心线程数是线程池中始终存在的线程数(除非设置了allowCoreThreadTimeOut)。
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,直到达到核心线程数。
如果线程池中的线程数已经大于或等于核心线程数,那么任务会被放入工作队列。
最大线程数
maximumPoolSize:最大线程数是线程池中允许的最大线程数。
如果工作队列满了,并且线程池中的线程数小于最大线程数,线程池会创建新的线程来执行任务。
设置过大会导致线程过多,可能会导致资源耗尽。
线程空闲时间
keepAliveTime:如果线程池中的线程数超过了核心线程数,多余的线程在空闲时间超过keepAliveTime时会被终止。
TimeUnit:TimeUnit是keepAliveTime的时间单位,可以是纳秒、微秒、毫秒、秒、分钟、小时和天等。
工作队列
workQueue:工作队列用于存储待执行的任务。当线程池中的线程数达到核心线程数时,新提交的任务会被放入工作队列中,等待线程池中的线程空闲时取出执行。
工作队列的类型和大小对线程池的行为和性能有着重要的影响。
Java提供了几种不同类型的工作队列,它们分别适用于不同的场景。
根据队列是否有界,可以将工作队列分为两大类:有界队列和无界队列。
有界队列和无界队列在线程池中的作用和行为有所不同。
对于有界队列,它有一个最大容量限制。当任务提交到线程池时,如果队列已满并且线程池中的线程数达到最大线程数,那么就会执行拒绝策略,通常是抛出一个异常或者执行预定义的处理方式。这种情况下,队列达到饱和时会触发拒绝策略。
相比之下,无界队列没有固定的容量限制。任务可以一直添加到队列中,直到系统资源耗尽为止。因此,即使线程池中的线程数达到最大线程数,任务仍然会被放置在队列中等待执行。在这种情况下,设置最大线程数(maximumPoolSize)并没有实际意义,因为队列永远都可以接受新任务。
有界队列
ArrayBlockingQueue
基于数组的有限阻塞队列,按照先进先出(FIFO)的原则对元素进行排序。
创建时必须指定容量,队列满时,线程池会创建新线程直至达到最大线程数;如果还是满的,则执行拒绝策略。
由于其固定长度,可以用来限制线程池中任务的最大数量。
LinkedBlockingQueue
基于链表结构的可选有界阻塞队列,按照FIFO原则对元素进行排序。
如果不指定容量,则默认为无边界的LinkedBlockingQueue,最大长度为Integer.MAX_VALUE。
LinkedBlockingDeque
基于链表的阻塞双端队列,实现了BlockingDeque接口。
可以从队列的两端插入和移除元素,支持工作窃取模式。
无界队列
PriorityBlockingQueue
一个具有优先级的无限阻塞队列,使用自然排序或者Comparator来决定元素的顺序。
元素按照优先级顺序被移除,优先级高的先被移除。
如果没有指定Comparator,那么队列中的元素必须实现Comparable接口,队列优先级的判断和排序就会依据这个接口的实现。
DelayQueue
一个支持延时获取元素的无界阻塞队列,队列使用PriorityBlockingQueue来实现。
队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。
常用于定时任务调度。
SynchronousQueue
一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作,反之亦然。
它相当于一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。
由于没有存储空间,所以maximumPoolSize参数通常被设置为较大的值,以避免拒绝任务。
LinkedTransferQueue
基于链表的阻塞队列,实现了BlockingQueue和TransferQueue接口。
相比于LinkedBlockingQueue,它多了tryTransfer和transfer方法,用于实现非阻塞和阻塞的单一元素传输。
队列选择
对于需要保证任务顺序的场景,可以使用ArrayBlockingQueue或LinkedBlockingQueue;
对于需要高吞吐量和任务处理的场景,可以使用SynchronousQueue;
对于需要任务优先级调度的场景,可以使用PriorityBlockingQueue。
线程工厂
threadFactory:线程工厂用于创建新线程,可以设置线程的名称、线程组、优先级等。
如果不指定,会使用默认的线程工厂。
拒绝策略

handler:当线程池和队列都满了时,线程池会使用拒绝策略来处理新提交的任务。
Java的ThreadPoolExecutor类提供了四种预定义的拒绝策略,同时也可以通过实现RejectedExecutionHandler接口来自定义拒绝策略。
AbortPolicy
AbortPolicy是ThreadPoolExecutor的默认拒绝策略。当线程池和任务队列都已满,并且无法再接受新的任务时,AbortPolicy会直接抛出一个RejectedExecutionException异常。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
try {
executor.execute(() -> System.out.println("任务1"));
executor.execute(() -> System.out.println("任务2"));
executor.execute(() -> System.out.println("任务3")); // 这将抛出RejectedExecutionException
} catch (RejectedExecutionException e) {
System.out.println("任务被拒绝执行:" + e.getMessage());
}CallerRunsPolicy
CallerRunsPolicy会让调用execute方法的线程(即提交任务的线程)来直接执行该任务,而不是由线程池中的线程执行。
查看代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
executor.execute(() -> {
System.out.println("任务1执行,线程:" + Thread.currentThread().getName());
try { Thread.sleep(1000);} catch (InterruptedException e) { e.printStackTrace();}
});
executor.execute(() -> {
System.out.println("任务2执行,线程:" + Thread.currentThread().getName());
try { Thread.sleep(1000);} catch (InterruptedException e) { e.printStackTrace();}
});
executor.execute(() -> System.out.println("任务3执行,线程:" + Thread.currentThread().getName()));
System.out.println("主线程继续执行");在这个例子中,任务3将由主线程执行,因为它是在主线程中提交的。在这个过程中,主逻辑的执行被任务3的执行打断,直到任务3执行完成,主线程才能继续执行后续的主逻辑。
任务1执行,线程:pool-1-thread-1
任务3执行,线程:main
主线程继续执行
任务2执行,线程:pool-1-thread-1DiscardPolicy
DiscardPolicy会简单地丢弃无法处理的任务,不会抛出任何异常。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
executor.execute(() -> System.out.println("任务1"));
executor.execute(() -> System.out.println("任务2"));
executor.execute(() -> System.out.println("任务3")); // 这将被丢弃,不会有任何输出DiscardOldestPolicy
DiscardOldestPolicy会丢弃任务队列中最老的任务,然后尝试重新提交新任务。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
executor.execute(() -> System.out.println("任务1"));
executor.execute(() -> System.out.println("任务2"));
executor.execute(() -> System.out.println("任务3")); // 这将导致任务1被丢弃,任务3将被执行在这个例子中,任务1将被丢弃,因为它是队列中最老的任务,而任务3将被重新提交并执行。
自定义拒绝策略
除了使用这些预定义的拒绝策略,我们还可以通过实现RejectedExecutionHandler接口来自定义拒绝策略。
以下是一个自定义拒绝策略的简单示例:
public class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 这里可以实现自定义的逻辑,例如记录日志、尝试重新提交任务等
System.out.println("任务被拒绝执行: " + r.toString());
}
}然后,可以在创建线程池时使用这个自定义的拒绝策略:
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new MyRejectedExecutionHandler());配置方法论
配置线程池参数时,应考虑应用程序的IO密集型或CPU密集型特性。
CPU密集型(CPU-bound)
CPU密集型任务是指那些主要依赖于CPU计算能力的任务,这些任务需要大量的计算,而输入/输出操作(读写磁盘、网络通信等)相对较少。以下是一些特点:
- 计算密集:这类任务涉及大量的算术计算、逻辑判断、循环处理等。
- CPU使用率高:在执行CPU密集型任务时,CPU的使用率通常会很高。
- 受CPU性能限制:任务的执行速度很大程度上取决于CPU的性能,如主频、核心数等。
- 例子:视频编码、3D渲染、科学计算(如模拟、数值分析)等。
IO密集型(I/O-bound)
IO密集型任务是指那些主要依赖于输入/输出操作的任务,这些任务涉及大量的数据读写、网络通信等,而CPU的计算需求相对较低。以下是一些特点:
- I/O操作频繁:这类任务涉及大量的磁盘读写、网络数据传输等。
- CPU使用率不一定高:由于CPU在等待I/O操作完成时会处于空闲状态,因此CPU使用率不一定很高。
- 受I/O性能限制:任务的执行速度很大程度上取决于I/O的性能,如磁盘读写速度、网络带宽等。
- 例子:数据库操作、文件处理、网络请求处理等。
总的来说,CPU密集型主要依赖CPU,而IO密集型主要依赖I/O子系统。
获取CPU核数
在Java中可以使用以下两种方法来查看机器的CPU核心数。
使用Runtime.getRuntime().availableProcessors()
这是最简单的方法,可以直接获取JVM可用的处理器数量。
public class Main {
public static void main(String[] args) {
int availableProcessors = Runtime.getRuntime().availableProcessors();
System.out.println("Available processors (cores): " + availableProcessors);
}
}这个方法返回的是JVM可用的处理器数量,它可能并不总是等同于物理CPU的核心总数,因为JVM可能会根据系统设置或其他因素限制可用的处理器数量。
使用java.lang.management包
通过java.lang.management包中的类也可以获取CPU核心数。
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
public class Main {
public static void main(String[] args) {
OperatingSystemMXBean osMxBean = ManagementFactory.getOperatingSystemMXBean();
int processors = osMxBean.getAvailableProcessors();
System.out.println("Available processors (cores): " + processors);
}
}在操作系统层面获取CPU核心数可以通过不同的命令或工具来实现,具体取决于使用的操作系统。
Windows
在Windows中,可以使用以下命令来获取CPU核心数:
使用PowerShell:
powershell(Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors使用命令提示符(cmd):
cmdwmic cpu get NumberOfCores或者获取逻辑处理器数:
cmdwmic cpu get NumberOfLogicalProcessors
macOS
在macOS中,可以使用sysctl命令:
sysctl -n hw.ncpuLinux
在Linux中,可以使用以下命令:
查看
/proc/cpuinfo文件:bashcat /proc/cpuinfo | grep 'processor' | wc -l这个命令会返回逻辑处理器的数量。
使用
nproc命令:bashnproc这个命令返回可用的处理器数量,通常是逻辑处理器的数量。
使用
lscpu命令:bashlscpu这个命令提供了详细的CPU信息,包括物理和逻辑核心数。
参数配置
对于CPU密集型任务,最好设置corePoolSize为CPU核数加一。这是因为,当一个线程正在执行一个CPU密集型任务时,该线程会一直占用一个CPU核心,而其他任务则无法使用这个核心。如果线程数少于CPU核心数,那么就无法充分利用所有CPU资源;而如果线程数多于CPU核心数,那么多余的线程就无法得到及时的CPU时间片,从而导致整体性能下降。因此,将corePoolSize设置为CPU核数加一,可以确保在所有CPU核心都被占用的情况下,还有一个额外的线程可以在有需要时立即投入运行。
对于IO密集型任务,设置corePoolSize为CPU核数的两倍是合适的。这样可以在一个线程等待I/O操作完成时,CPU可以切换到另一个线程继续工作,从而有效利用CPU的空闲时间。IO密集型任务的主要特点是,它们往往花费大量时间在等待I/O操作,如磁盘读写或网络通信,而这期间CPU处于非活跃状态。因此,配置更多的线程可以确保在某个线程等待I/O时,其他线程可以继续使用CPU资源,这有助于提升系统的整体吞吐量和响应速度。
线程池的类型
Java并发包还提供了一些预定义的线程池工厂方法,可以方便地创建不同类型的线程池。
这些预定义的线程池都是通过Executors工厂类创建的。
- FixedThreadPool:
- 特点:固定大小的线程池,核心线程的即为最大的线程数量,其中线程数量始终不变。当线程池中的所有线程都处于活动状态时,新任务将在队列中等待,不能创建新线程,需要等到可用线程。
- 适用场景:适用于负载较重的服务器,需要限制线程数量以避免资源耗尽。
- SingleThreadExecutor:
- 特点:只有一个线程的线程池,确保所有任务都在同一个线程中按顺序执行。
- 适用场景:适用于需要保证任务顺序执行的场景,例如某些序列化的任务。
- CachedThreadPool:
- 特点:根据需要创建新线程的线程池。如果线程池中的线程在短时间内(比如60秒)没有被使用,就会被回收。没有核心线程,非核心线程的数量为
Integer.max_value。 - 适用场景:适用于执行大量短期异步任务的场景,线程池可以动态地增加线程数量以应对负载。
- 特点:根据需要创建新线程的线程池。如果线程池中的线程在短时间内(比如60秒)没有被使用,就会被回收。没有核心线程,非核心线程的数量为
- ScheduledThreadPool:
- 特点:可以定时执行任务的线程池,类似于
Timer类,但功能更强大。有一定的核心线程,非核心线程的数量为Integer.max_value。 - 适用场景:适用于需要定期执行任务或延迟执行任务的应用场景。
- 特点:可以定时执行任务的线程池,类似于
- WorkStealingPool(Java 8引入):
- 特点:基于ForkJoinPool实现的线程池,其中线程可以窃取其他线程的任务来执行,以提高效率。
- 适用场景:适用于需要大量计算且能够并行处理的应用场景。
线程池的创建
下面是通过Executors工厂类创建线程池的例子:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
ExecutorService workStealingPool = Executors.newWorkStealingPool();Executors工厂类提供的预定义线程池在默认情况下可能不够灵活,并且在不了解其内部实现的情况下,可能会带来潜在的风险。比如
- FixedThreadPool 和 SingleThreadExecutor:
- 这两个线程池使用无界队列
LinkedBlockingQueue来存储等待执行的任务。如果任务提交的速度超过了线程处理任务的速度,队列可能会无限增长,从而占用大量内存,甚至导致内存溢出(OOM)。
- 这两个线程池使用无界队列
- CachedThreadPool:
CachedThreadPool使用同步队列SynchronousQueue,它没有容量限制,这意味着任何提交的任务都需要一个线程来执行。由于线程池的最大线程数设置为Integer.MAX_VALUE,如果任务提交得非常快,线程池可能会创建大量线程,这可能导致系统资源耗尽,从而引发OOM。
- ScheduledThreadPool :
ScheduledThreadPool使用延迟工作队列DelayedWorkQueue,它也是一个无界队列。如果调度器的任务执行时间过长或者任务被不断延迟,队列中的任务可能会无限增长,导致内存占用增加,甚至OOM。
自定义线程池
为了避免Executors工厂类提供的线程池存在的潜在问题,推荐的做法是直接使用ThreadPoolExecutor的构造函数来创建线程池,这样我们可以更清楚地了解线程池的配置,并能够根据应用程序的需求来调整线程池的参数,例如核心线程数、最大线程数、线程空闲时间、任务队列等。
以下是一个使用ThreadPoolExecutor自定义线程池的示例:
int corePoolSize = 10; // 核心线程数
int maximumPoolSize = 20; // 最大线程数
long keepAliveTime = 60; // 线程空闲时间
TimeUnit unit = TimeUnit.SECONDS; // 空闲时间单位
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // 任务队列,这里使用了有界队列
ThreadFactory threadFactory = new MyThreadFactory(); // 线程工厂,可以自定义线程名称和优先级等
RejectedExecutionHandler handler = new MyRejectedExecutionHandler(); // 拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);线程池的状态
Java中的线程池通过ThreadPoolExecutor类实现,它有几个关键的状态和状态转换,这些状态定义在ThreadPoolExecutor类内部的ctl变量中,这个变量同时包含了线程池的运行状态(runState)和线程池中有效线程的数量(workerCount)。
ThreadPoolExecutor相关源码
查看代码
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;线程池的状态转换是原子性的,并且是顺序执行的。以下是线程池可能处于的状态:
RUNNING:
线程池能够接受新任务,并对提交的任务进行处理。
调用
shutdown()方法会转换为SHUTDOWN状态。
SHUTDOWN:
- 线程池不再接受新任务,但会继续处理阻塞队列中剩余的任务。
- 当阻塞队列中的所有任务都被执行完成后,线程池中的线程会逐渐被关闭,状态转换为TIDYING。
- 调用
shutdownNow()方法会转换为STOP状态。
STOP:
- 线程池不再接受新任务,也不会处理阻塞队列中的任务,并且会尝试中断正在执行的任务。
- 当所有任务都被尝试中断并且所有线程都已经终止时,状态转换为TIDYING。
TIDYING:
- 所有任务都已终止,线程池的工作线程数为0,线程池的状态会变为TIDYING。
- 将会执行
terminated()钩子方法,这是一个扩展点,可以用来做一些清理工作。
TERMINATED:
terminated()钩子方法执行完成后,线程池的状态会变为TERMINATED,这时线程池已经完全终止。

线程池的状态转换是不可逆的,一旦进入TERMINATED状态,线程池就不能再被使用了。在实际应用中,通常需要根据业务需求来决定何时调用shutdown()或shutdownNow()方法来关闭线程池,并处理线程池关闭过程中的资源清理和状态通知。
线程池的状态检查和转换是通过ctl变量的原子操作来实现的,这样可以确保在多线程环境下的线程安全。
线程池关闭
Java线程池提供了几种关闭线程池的方法,主要包括shutdown()和shutdownNow()两种。
shutdown
shutdown()方法用于平滑地关闭线程池,它会等待所有已提交的任务执行完成,但不接受新任务。调用此方法后,已提交的任务会继续执行,而所有后续的任务提交(通过execute()或submit()方法)都会被拒绝,并且会调用RejectedExecutionHandler接口的rejectedExecution()方法。
shutdownNow
shutdownNow()方法用于尝试立即关闭线程池,它会尝试停止所有正在执行的任务,并返回尚未开始执行的任务列表。与shutdown()不同,shutdownNow()会尝试立即中断所有工作线程,并尝试停止所有正在执行的任务。
线程池执行流程
创建线程池
首先,需要创建一个ThreadPoolExecutor实例,并指定线程池的核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、线程空闲时间(keepAliveTime)、时间单位(unit)以及任务队列(workQueue)。
提交任务
提交任务的方法
提交任务到线程池是线程池执行流程中的关键步骤。在Java中,可以通过ExecutorService接口的submit()和execute()方法来提交任务。
execute
execute()方法用于提交不需要返回值的任务。这个方法接受一个Runnable对象作为参数。
execute() 方法无法处理任务抛出的异常,如果任务执行过程中抛出异常,线程池将无法捕获并处理该异常。
submit
submit()方法用于提交需要返回值的任务。这个方法接受一个Callable对象或者Runnable对象,并返回一个Future对象,通过这个对象可以获取任务的执行结果或者检查任务是否完成。
当调用submit()方法时,线程池内部会将Callable或Runnable对象包装成一个FutureTask对象,然后按照execute()方法的规则来处理这个FutureTask。
如果任务成功完成,可以通过Future对象的get()方法来获取任务的执行结果。如果任务执行过程中遇到异常,get()方法会抛出ExecutionException异常。
任务处理规则
当一个任务通过execute()或submit()方法提交给线程池时,线程池会根据当前线程池的状态和配置来决定如何处理该任务。
如果当前线程数小于核心线程数,线程池会创建一个新的线程来执行任务。
如果当前线程数大于或等于核心线程数,并且任务队列未满,线程池会将任务放入任务队列中等待执行。
如果任务队列已满,并且当前线程数小于最大线程数,线程池会创建一个新的线程来执行任务。
如果任务队列已满,并且当前线程数大于或等于最大线程数,线程池会根据拒绝策略来处理任务。
执行任务
当一个线程从任务队列中获取到任务后,它会执行任务的run()方法。如果是通过submit()方法提交的任务,它实际上会执行FutureTask的run()方法,FutureTask会调用原始任务的call()方法,并保存其结果。
如果任务执行过程中遇到异常,异常会被捕获并保存到Future对象中,可以通过Future对象的get()方法获取到异常。
关闭线程池
当不再需要线程池时,可以通过调用shutdown()方法来关闭线程池。这将使线程池不再接受新的任务,但会继续执行任务队列中的任务。
如果需要立即关闭线程池,可以使用shutdownNow()方法。这将尝试停止所有正在执行的任务,并返回任务队列中尚未开始执行的任务列表。
线程池的监控
线程池监控是确保线程池正常运行和生产环境中性能调优的关键。在Java中,ThreadPoolExecutor提供了一些方法来获取线程池的当前状态和性能指标。
线程池状态信息
getPoolSize():返回线程池中当前线程的数量。getActiveCount():返回线程池中正在执行任务的线程数量。getLargestPoolSize():返回线程池历史上曾达到的最大线程数量。getTaskCount():返回线程池已经执行的任务总数(包括已经完成的和正在执行的)。getCompletedTaskCount():返回线程池已经完成的任务总数。isShutdown():返回线程池是否已经关闭。isTerminating():返回线程池是否正在终止,即正在执行关闭操作但尚未完全终止。isTerminated():返回线程池是否已经完全终止。
线程池性能指标
- 线程利用率:通过
getActiveCount()和getPoolSize()可以计算线程的利用率,即正在执行任务的线程数除以总线程数。 - 任务提交速率:通过对比
getTaskCount()的值,可以了解任务提交的速率。 - 任务完成速率:通过对比
getCompletedTaskCount()的值,可以了解任务的完成速率。 - 队列容量:通过
getQueue()可以获取任务队列的容量,结合getPoolSize()和getActiveCount()可以了解队列的繁忙程度。
