Java线程池是在处理并发编程时非常关键的一部分,主要用于管理被多个任务共享的一组固定数量的线程资源。
线程池的关键参数详解
核心线程数(corePoolSize)
- 这是线程池初始化时的线程数量,即使这些线程处于空闲状态,也不会被回收。这个参数对系统性能有重要影响,设置过小可能导致处理任务时延迟增加,设置过大可能会浪费系统资源。
最大线程数(maximumPoolSize)
- 线程池允许同时运行的最大线程数量。当工作队列满了且当前运行的线程数小于最大线程数时,线程池会创建新的线程来处理任务。
存活时间(keepAliveTime)和时间单位(unit)
- 当线程数量超过核心线程数时,这是超出数量的空闲线程在被终止前可以保持空闲的最长时间。单位是一个枚举,表示时间单位,如
TimeUnit.SECONDS
。
- 当线程数量超过核心线程数时,这是超出数量的空闲线程在被终止前可以保持空闲的最长时间。单位是一个枚举,表示时间单位,如
工作队列(workQueue)
- 用于存放等待执行的任务的阻塞队列。常见的选项包括直接交付队列
SynchronousQueue
、无界队列LinkedBlockingQueue
和有界队列ArrayBlockingQueue
。
- 用于存放等待执行的任务的阻塞队列。常见的选项包括直接交付队列
线程工厂(ThreadFactory)
- 创建新线程的工厂。可以自定义线程工厂来设置线程的名字、优先级等属性。
拒绝策略(RejectedExecutionHandler)
- 当队列满了并且线程数达到最大值时,新提交的任务如何处理。常见的策略有
ThreadPoolExecutor.AbortPolicy
(抛出异常)、ThreadPoolExecutor.DiscardPolicy
(丢弃任务,不抛出异常)、ThreadPoolExecutor.DiscardOldestPolicy
(丢弃队列最前面的任务),和ThreadPoolExecutor.CallerRunsPolicy
(调用者所在的线程来执行任务)。
- 当队列满了并且线程数达到最大值时,新提交的任务如何处理。常见的策略有
示例:使用ThreadPoolExecutor自定义线程池
以下是如何直接使用ThreadPoolExecutor
来创建一个自定义的线程池的示例:
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
ExecutorService threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, unit, workQueue,
threadFactory, handler);
for (int i = 0; i < 10; i++) {
int taskNo = i;
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task " + taskNo);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
threadPool.shutdown();
}
}
Executors类常用工厂方法
newFixedThreadPool(int nThreads)
- 创建一个固定线程数的线程池。所有提交的任务都使用这些线程处理。适用于任务量预知,需要控制并发数量的场景。
示例代码:
ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { int taskNo = i; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " is executing task " + taskNo); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown();
newSingleThreadExecutor()
- 创建一个单线程的执行器,这可以保证所有任务按照提交顺序串行执行。适用于需要单线程执行任务,保证顺序性和一致性的场景。
示例代码:
ExecutorService executor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++) { int taskNo = i; executor.execute(() -> { System.out.println("Executing task " + taskNo); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown();
newCachedThreadPool()
- 创建一个可缓存线程的线程池,如果线程池的大小超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。适用于任务数动态变化的场景。
示例代码:
ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { int taskNo = i; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " is executing task " + taskNo); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown();
newScheduledThreadPool(int corePoolSize)
- 创建一个固定数量的线程来执行定时或周期性任务。适用于需要多个后台线程执行周期任务,同时为了资源的有效利用需要限制线程数量的场景。
示例代码:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); Runnable beeper = () -> System.out.println("beep"); // 安排任务每10秒钟执行一次 ScheduledFuture<?> beeperHandle = scheduler.scheduleAtFixedRate(beeper, 0, 10, TimeUnit.SECONDS); // 安排在60秒后取消任务 scheduler.schedule(() -> beeperHandle.cancel(true), 60, TimeUnit.SECONDS);
关键方法
- execute(Runnable command): 提交一个Runnable任务用于执行。
- submit(Callable
task) : 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的Future。 - shutdown(): 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
- shutdownNow(): 尝试停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
状态管理
线程池的状态变化是通过内部的runState
和workerCount
进行管理的,不同状态下线程池的行为也不同,比如在SHUTDOWN
状态下,线程池不接受新任务,但会继续处理队列中的旧任务。
使用建议和最佳实践
- 资源管理:对于
newCachedThreadPool
和newScheduledThreadPool
等可能创建大量线程的工厂方法,需要注意系统资源的使用,避免创建过多线程导致资源耗尽。 - 任务类型和线程池策略匹配:选择最适合任务特性的线程池类型,如IO密集型、CPU密集型或混合型任务。
- 优雅关闭:在应用程序结束时,应调用
shutdown()
或shutdownNow()
方法来优雅地关闭线程池,释放资源。
通过合理选择和使用Executors
类提供的工厂方法,可以有效地管理线程资源,提高程序的并发性能和稳定性。
评论 (0)