手动创建线程池,效果会更好哦。 建议使用ThreadPoolExecutor线程池创建

手动创建线程池,效果会更好哦。
Inspection info:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
  允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
  允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

Positive example 1//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());



Positive example 2ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown



Positive example 3<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />

<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
//in code
userThreadPool.execute(thread);

线程池执行任务逻辑和线程池参数的关系
在这里插入图片描述

  • 执行逻辑说明:

  • 判断核心线程数是否已满,核心线程数大小和corePoolSize参数有关,未满则创建线程执行任务

  • 若核心线程池已满,判断队列是否满,队列是否满和workQueue参数有关,若未满则加入队列中

  • 若队列已满,判断线程池是否已满,线程池是否已满和maximumPoolSize参数有关,若未满创建线程执行任务

  • 若线程池已满,则采用拒绝策略处理无法执执行的任务,拒绝策略和handler参数有关

ThreadPoolExecutor是线程池中最核心的一个类,其继承关系如下
在这里插入图片描述其中Executor是线程池的顶级接口,接口中只定义了一个方法 void execute(Runnable command);线程池的操作方法都是定义子在ExecutorService子接口中的,所以说ExecutorService是线程池真正的接口。

Executors创建返回ThreadPoolExecutors对象
Executors创建返回 静态ThreadPoolExecutor对象 的方法共有三种:

Executors#newCachedThreadPool => 创建可缓存的线程池
Executors#newSingleThreadExecutor => 创建单线程的线程池
Executors#newFixedThreadPool => 创建固定长度的线程池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}



public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


ThreadPoolExecutor的构造函数共有四个,但最终调用的都是同一个:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

构造函数参数说明:

corePoolSize => 线程池核心线程数量
maximumPoolSize => 线程池最大数量
keepAliveTime => 空闲线程存活时间
unit => 时间单位
workQueue => 线程池所使用的缓冲队列
threadFactory => 线程池创建线程使用的工厂
handler => 线程池对拒绝任务的处理策略

OOM异常测试

理论上会出现OOM异常,必须测试一波验证之前的说法:

测试类:TaskTest.java

public class TaskTest {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        int i = 0;
        while (true) {
            es.submit(new Task(i++));
        }
    }
}

使用Executors创建的CachedThreadPool,往线程池中无限添加线程

在启动测试类之前先将JVM内存调整小一点,不然很容易将电脑跑出问题【别问我为什么知道,是铁憨憨甜没错了!!!】,在idea里:Run -> Edit Configurations
在这里插入图片描述
JVM参数说明:

-Xms10M => Java Heap内存初始化值
-Xmx10M => Java Heap内存最大值

运行结果:

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler 
in thread "main"Disconnected from the target VM, address: '127.0.0.1:60416',
 transport: 'socket'

创建到3w多个线程的时候开始报OOM错误

如何定义线程池参数

CPU密集型:线程池的大小推荐为CPU数量+1。CPU数量可以根据 Runtime.getRuntime().availableProcessors() 方法获取

IO密集型:CPU数量 * CPU利用率 *(1 + 线程等待时间/线程CPU时间)

混合型 => 将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,从而使每个线程池可以根据各自的工作负载来调整

阻塞队列 => 推荐使用有界队列,有界队列有助于避免资源耗尽的情况发生

拒绝策略 => 默认采用的是AbortPolicy拒绝策略,直接在程序中抛出RejectedExecutionException异常【因为是运行时异常,不强制catch】,这种处理方式不够优雅。处理拒绝策略有以下几种比较推荐:

  • 在程序中捕获RejectedExecutionException异常,在捕获异常中对任务进行处理。针对默认拒绝策略

  • 使用CallerRunsPolicy拒绝策略,该策略会将任务交给调用execute的线程执行【一般为主线程】,此时主线程将在一段时间内不能提交任何任务,从而使工作线程处理正在执行的任务。此时提交的线程将被保存在TCP队列中,TCP队列满将会影响客户端,这是一种平缓的性能降低

  • 自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可

  • 如果任务不是特别重要,使用DiscardPolicy和DiscardOldestPolicy拒绝策略将任务丢弃也是可以的

如果使用Executors的静态方法创建ThreadPoolExecutor对象,可以通过使用Semaphore对任务的执行进行限流也可以避免出现OOM异常。

规避资源耗尽的风险,推荐写法:

//获取系统处理器个数,作为线程池数量
int nThreads = Runtime.getRuntime().availableProcessors();
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
 
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(nThreads , 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
<think>好的,用户想知道Java手动创建线程池的好处与优势。首先,我需要回顾一下线程池的基本概念以及为什么需要手动创建而不是使用内置的。 线程池的主要目的是减少线程创建和销毁的开销,提高系统性能。Java提供了Executors工具类来快速创建线程池,比如newFixedThreadPool、newCachedThreadPool等。但根据阿里开发手册,推荐手动创建ThreadPoolExecutor,这可能是因为Executors的预设配置存在一些问题,比如newFixedThreadPool使用无界队列可能导致内存溢出,而newCachedThreadPool可能创建过多线程导致资源耗尽。 手动创建线程池的好处应该包括更灵活的参数配置,比如核心线程数、最大线程数、队列类型及容量,还有自定义拒绝策略。这样可以更好地控制资源使用,避免OOM等问题。另外,手动创建允许根据具体业务需求调整线程池行为,比如设置线程存活时间、自定义线程工厂等,这些在Executors的工厂方法中是做不到的。 需要确认用户是否了解线程池的基本参数和原理,可能需要在回答中简要说明这些参数的作用。同时,引用中的资料提到线程池的优势在于透明和可控,手动创建可以避免预设实现的一些缺陷,比如newCachedThreadPool可能无限制增加线程的问题。 可能还需要提到资源管理的优化,手动配置队列大小和拒绝策略可以防止任务堆积,提升系统稳定性。此外,线程池的监控和调优也是手动创建的优势,比如通过扩展ThreadPoolExecutor的方法来监控任务执行情况。 要确保回答结构清晰,分点列出优势,并结合引用内容说明。比如引用[2]提到ThreadPoolExecutor的参数控制,引用[1]比较了手动创建线程和线程池的资源管理差异,引用[3]则提到线程创建的复杂性,但线程池可以简化这部分。 总结起来,手动创建线程池的优势包括:参数可控、避免资源耗尽、自定义拒绝策略、更好的资源管理、支持监控和扩展。需要将这些点用简洁的中文表达,并适当引用资料支持。</think>### Java 手动创建线程池的优势分析 在 Java 中,手动创建线程池(通过`ThreadPoolExecutor`类)相比直接使用`Executors`工具类提供的预设线程池,具有以下核心优势: --- #### 1. **参数灵活可控,避免资源耗尽风险** `ThreadPoolExecutor`允许开发者自定义以下关键参数: - **核心线程数(corePoolSize)**:长期存活的线程数量。 - **最大线程数(maximumPoolSize)**:线程池能容纳的最大线程数。 - **任务队列类型及容量(workQueue)**:例如有界队列`ArrayBlockingQueue`,可防止无界队列导致的内存溢出。 - **线程存活时间(keepAliveTime)**:非核心线程的空闲回收时间。 - **拒绝策略(RejectedExecutionHandler)**:当任务超过队列容量时,自定义处理逻辑(如丢弃、重试或记录日志)。 通过精细控制这些参数,可以避免`Executors`预设线程池的潜在问题。例如: - `Executors.newCachedThreadPool()`使用无容量队列,可能导致线程数激增,耗尽 CPU 和内存资源[^2]。 - `Executors.newFixedThreadPool()`使用无界队列,任务堆积时可能引发内存溢出(OOM)。 --- #### 2. **自定义拒绝策略,提升系统健壮性** 手动创建线程池时,可指定以下拒绝策略(或自定义实现): - **AbortPolicy**(默认):直接抛出异常,强制开发者处理任务过载问题。 - **CallerRunsPolicy**:由提交任务的线程执行任务,降低提交速度。 - **DiscardOldestPolicy**:丢弃队列中最旧的任务,腾出空间。 - **DiscardPolicy**:静默丢弃新任务。 例如,在电商秒杀场景中,可以通过自定义拒绝策略记录超限任务并异步重试,避免服务崩溃。 --- #### 3. **资源管理与性能优化** - **减少线程创建开销**:复用线程池中的线程,避免频繁创建和销毁线程的系统资源消耗[^1]。 - **控制并发量**:通过最大线程数限制,防止高并发场景下线程竞争导致的性能下降。 - **任务队列隔离**:针对不同业务类型创建独立线程池,避免相互影响(如订单处理与日志记录分离)。 --- #### 4. **支持监控与扩展** 通过继承`ThreadPoolExecutor`并重写其方法(如`beforeExecute`、`afterExecute`),可以实现: - **任务执行时间统计** - **线程池负载监控** - **异常捕获与告警** 例如: ```java public class CustomThreadPool extends ThreadPoolExecutor { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); // 记录任务执行完成时间或异常 } } ``` --- ### 总结 手动创建线程池的优势主要在于**精细化资源控制**和**风险规避**,尤其适用于高并发、高可靠性的系统场景。相比之下,`Executors`的预设线程池更适合简单的测试或低并发需求,但在生产环境中需谨慎使用[^3]。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值