java并发之线程池使用

前置推荐阅读

线程简介:初识java线程-CSDN博客
java锁介绍:java高并发之锁-CSDN博客

线程池简介

Java线程池是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要目的是减少在创建和销毁线程时所产生的性能开销。通过重用已经创建的线程来执行新的任务,线程池提高了程序的响应速度,并且提供了更好的系统资源管理。

Java中的线程池主要由java.util.concurrent包下的Executors类和ThreadPoolExecutor类提供。

线程池的主要作用包括:

  1. 减少开销:通过重用已经创建的线程来执行新的任务,减少了每次任务都创建和销毁线程的开销。
  2. 提高响应速度:当任务到达时,线程池可以立即在工作线程中执行,而不需要等待线程的创建。
  3. 提高线程的可管理性:线程池提供了更好的线程管理,可以对线程进行监控和调整。
  4. 资源优化:通过限制线程池中的线程数量,可以防止因为创建过多线程而消耗过多资源,导致系统崩溃。

Java中的线程池

Java提供了以下几种线程池:

  1. CachedThreadPool:一个会根据需要创建新线程的线程池,对于短生命周期的异步任务非常合适。它会尝试缓存线程并重用,当线程空闲超过60秒时会被回收。
  2. FixedThreadPool:拥有固定数量线程的线程池,适用于负载较重的服务器。
  3. SingleThreadExecutor:单个后台线程的Executor,保证所有任务按顺序执行。
  4. ScheduledThreadPool:用于延迟执行或定期执行任务的线程池。
  5. WorkStealingPool:一个可以创建足够多线程以窃取其他队列中任务的线程池,适用于负载较重的并行计算。

如何使用线程池

使用线程池通常涉及以下几个步骤:

  1. 创建线程池。
  2. 提交任务到线程池。
  3. 关闭线程池。

示例代码:

复制代码
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(4);

// 提交任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        // 任务代码
    }
});

// 提交带返回值的任务
Future<String> future = threadPool.submit(() -> {
    // 任务代码
    return "result";
});

// 关闭线程池
threadPool.shutdown();

线程池参数的含义

  1. corePoolSize:核心线程数,线程池中始终保持的线程数量。
  2. maximumPoolSize:最大线程数,线程池中允许的最大线程数量。
  3. workQueue:工作队列,用于存放待执行任务的阻塞队列。
  4. threadFactory:线程工厂,用于创建新线程。
  5. handler:拒绝策略,当任务太多,无法被线程池及时处理时,采取的策略。

如何理解IO密集型和CPU密集型

  • IO密集型任务:任务执行过程中,大部分时间在等待IO操作(如网络请求、文件读写等)完成,CPU计算时间相对较少。
  • CPU密集型任务:任务执行过程中,大部分时间在进行CPU计算,IO操作相对较少。

线程数应该如何正确设置

  1. CPU密集型任务:线程数应该接近或等于CPU核心数,因为线程数过多会导致上下文切换频繁,反而降低效率。
  2. IO密集型任务:线程数可以设置得更多,因为线程在等待IO操作时,可以被操作系统挂起,让出CPU给其他线程使用。

一个简单的经验公式是:

  • CPU密集型:线程数 = CPU核心数
  • IO密集型:线程数 = 2 * CPU核心数

但这个公式只是一个起点,实际的最优线程数可能需要根据具体的应用场景和系统资源进行调整和测试。

线程池拒绝策略

Java线程池中的拒绝策略(RejectedExecutionHandler)是在当前线程池中的线程数量达到最大线程数,并且工作队列也满了时,如何处理新提交的任务。Java提供了四种内置的拒绝策略:

  1. AbortPolicy :这是默认的拒绝策略。当任务队列已满时,直接抛出 RejectedExecutionException 异常,不执行该任务。这种策略简单且直接,但可能导致任务丢失或系统崩溃,适用于对系统稳定性要求高的场景。

  2. CallerRunsPolicy:当任务队列已满时,调用任务的线程会尝试运行该任务。如果调用线程忙,则会创建一个新线程来执行任务。这种策略可以保证任务不会被丢失,但可能会导致调用线程阻塞,影响其他任务的执行,适用于系统稳定性重要且可以容忍任务延迟的场景。

  3. DiscardPolicy:当任务队列已满时,直接丢弃该任务,不执行也不报错。这种策略可能会导致任务丢失,但不会阻塞调用线程,也不会抛出异常,适用于可以容忍任务丢失的场景。

  4. DiscardOldestPolicy:当任务队列已满时,丢弃队列中最旧的任务,然后尝试再次提交被拒绝的任务。这种策略可以避免任务丢失,但可能导致队列中重要的任务被丢弃,适用于任务的重要性相对较低,且希望尽量处理新提交的任务的场景。

除了这四种策略,开发者还可以根据实际需求自定义拒绝策略。自定义拒绝策略需要实现 RejectedExecutionHandler 接口,并重写 rejectedExecution 方法。自定义策略可以更加灵活地处理被拒绝的任务,例如将任务保存到数据库、消息队列等,以便后续处理。

选择合适的拒绝策略对于确保线程池在高并发环境下的稳定性和性能至关重要。开发者应根据具体的应用场景和需求来选择或自定义拒绝策略,以优化线程池性能并避免线程池饱和导致的各种问题。

Java线程池的拒绝策略是处理超出线程池处理能力任务的一种机制。当线程池中的线程数量和工作队列都达到最大容量时,新提交的任务将触发拒绝策略。Java提供了四种标准的拒绝策略:

  1. AbortPolicy:抛出异常,拒绝任务,这是默认策略。
  2. CallerRunsPolicy:任务由提交任务的调用者线程运行,避免了任务的丢失,但可能会引起性能问题。
  3. DiscardPolicy:静默丢弃任务,不抛出异常,可能会导致任务丢失。
  4. DiscardOldestPolicy:丢弃最旧的未处理任务,并尝试再次提交新任务。

选择合适的拒绝策略对于确保线程池的稳定性和性能至关重要。开发者可以根据应用场景的需求,选择预定义的策略或自定义策略来处理超出线程池容量的任务。例如,对于关键任务,可能需要选择不会丢失任务的策略;而对于可以容忍一定丢失的非关键任务,可以选择DiscardPolicy或DiscardOldestPolicy。自定义拒绝策略可以提供更灵活的处理方式,如将任务存储在数据库或消息队列中以供后续处理。正确配置和使用拒绝策略,可以帮助开发者在高并发环境下有效地管理线程资源,保护系统免受过载影响。

相关推荐
程序员JerrySUN10 小时前
别再把 HTTPS 和 OTA 看成两回事:一篇讲透 HTTPS 协议、安全通信机制与 Mender 升级加密链路的完整文章
android·java·开发语言·深度学习·流程图
郝学胜-神的一滴10 小时前
系统设计与面向对象设计:两大设计思想的深度剖析
java·前端·c++·ue5·软件工程
myloveasuka10 小时前
[Java]子类到底能继承父类中的哪些东西?继承中成员变量/方法访问特点---就近原则
java·开发语言
umeelove3510 小时前
vscode配置django环境并创建django项目(全图文操作)
java
x-cmd10 小时前
[260307] x-cmd v0.8.6:新增 gpt-5.4 模型支持,sudo/os/hostname/cpu 等模块文档更新
java·数据库·gpt·sudo·x-cmd·googel
PPPPickup10 小时前
深信服公司---java实习生后端一二面询问
java·后端·ai
架构师沉默10 小时前
为什么很多大厂 API 不再使用 PUT 和 DELETE?
java·后端·架构
YDS82910 小时前
SpringCloud —— Elasticsearch的DSL查询
java·elasticsearch·搜索引擎·spring cloud
亚马逊云开发者10 小时前
你的 AI Agent 在裸奔吗?四层防护方案,从权限到审计一次讲透
java
意疏10 小时前
openJiuwen实战:用AsyncCallbackFramework为Agent增强器添加可观测性
java·服务器·前端