线程池的介绍
在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-1
DiscardPolicy
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.ncpu
Linux
在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()
可以了解队列的繁忙程度。