任务执行
无限制创建线程的不足
在讨论并发编程和多线程处理的场景中,“为每个任务分配一个线程”这种策略存在一系列的不足,特别是在生产环境中需要创建大量线程时。以下是这种策略的主要问题:
-
线程生命周期的开销:线程的创建和销毁过程需要时间和系统资源。这会导致处理请求的延迟,并增加JVM和操作系统的负担。对于需要处理大量轻量级请求的应用(如大多数服务器应用),为每个请求创建一个新线程会消耗大量计算资源。
-
资源消耗:活跃的线程消耗系统资源,尤其是内存。如果可运行的线程数量超过可用处理器的数量,一些线程将处于闲置状态。这些大量的空闲线程不仅占用内存,还增加了垃圾回收器的负担,并在竞争CPU资源时产生额外性能开销。如果已有足够多的线程使所有CPU保持忙碌,再创建更多线程实际上会降低性能。
-
稳定性问题:可创建线程的数量有限制,这个限制因平台不同而异,受多种因素影响,包括JVM启动参数、Thread构造函数中的栈大小请求以及底层操作系统对线程的限制等。超出这些限制可能导致抛出
OutOfMemoryError
异常,从这种错误中恢复非常危险,更可行的方法是设计程序以避免超出限制。 -
系统吞吐率和性能:在一定的范围内,增加线程可以提高系统吞吐率,但超出这个范围后,继续创建线程只会降低程序执行速度。过多地创建线程可能导致应用程序崩溃。
-
高负载下的风险:在应用程序部署后并处于高负载下运行时,无限制地创建线程会导致问题暴露,恶意用户或过多用户可能使Web服务器负载达到阈值并崩溃。对于需要高可用性并能在高负载情况下平稳降低性能的服务器而言,这将是一个严重的故障。
Executor框架
Executor基于生产者-消费者模式,其中提交任务的操作相当于生产者(生成待完成的工作单元),执行任务的线程则相当于消费者(执行这些工作单元)。在程序中实现生产者-消费者设计时,使用Executor是最简单的方式。
使用线程池执行任务相比为每个任务分配一个线程有更多的优势。线程池通过重用现有线程而不是创建新线程,减少了线程创建和销毁的开销。此外,由于工作线程通常已经存在,任务的执行不会因等待线程创建而延迟,这提高了响应性。适当调整线程池大小可以确保有足够的线程使处理器保持忙碌,同时防止过多线程竞争资源导致应用程序耗尽内存或失败。
Java类库提供了灵活的线程池和一些有用的默认配置。可以通过调用Executors
中的静态工厂方法来创建线程池:
newFixedThreadPool
:创建一个固定长度的线程池,达到最大数量后不再变化,如果线程因异常结束,会补充新线程。newCachedThreadPool
:创建一个可缓存的线程池,根据需求增加或回收空闲线程,线程池规模无限制。newSingleThreadExecutor
:创建单个工作线程的Executor,按任务队列顺序串行执行,如FIFO、LIFO、优先级。newScheduledThreadPool
:创建一个固定长度的线程池,以延迟或定时方式执行任务,类似于Timer。
newFixedThreadPool
和newCachedThreadPool
返回ThreadPoolExecutor
实例,可以直接用来构造专门用途的executor。第8章将深入讨论线程池的配置选项。
TaskExecutionWebServer
中的Web服务器使用了一个带有有界线程池的Executor。通过execute
方法将任务提交到工作队列中,工作线程反复地从队列中取出任务并执行它们。
好的,以下是为您整理后的内容:
为了解决执行服务的生命周期问题,Executor
扩展了ExecutorService
接口,添加了一些用于生命周期管理的方法,以及一些便于任务提交的方法。ExecutorService
的生命周期有三种状态:运行、关闭和已终止。初始创建时处于运行状态。
void shutdown();
:启动平缓的关闭过程,不再接受新任务,等待已提交的任务完成。List<Runnable> shutdownNow();
:尝试粗暴关闭,取消所有正在执行的任务,不启动尚未开始的任务。boolean isShutdown();
:检查是否开始关闭过程。boolean isTerminated();
:检查所有任务是否完成,ExecutorService是否处于终止状态。boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
:等待直到所有任务完成或超时,或当前线程被中断,之后返回true;如果已经进入终止状态,立刻返回true。
在ExecutorService
关闭后提交的任务将由“拒绝执行处理器”处理,它会抛弃任务或使execute
方法抛出RejectedExecutionException
。通常在调用awaitTermination
后会立即调用shutdown
以同步关闭ExecutorService
。
Timer
类用于管理延迟任务(如“100ms后执行该任务”)和周期任务(如“每10ms执行一次该任务”)。但由于Timer
存在一些缺陷,建议使用ScheduledThreadPoolExecutor
来代替。ScheduledThreadPoo