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。自定义拒绝策略可以提供更灵活的处理方式,如将任务存储在数据库或消息队列中以供后续处理。正确配置和使用拒绝策略,可以帮助开发者在高并发环境下有效地管理线程资源,保护系统免受过载影响。

相关推荐
@糊糊涂涂17 分钟前
MAC借助终端上传jar包到云服务器
java·服务器·macos·jar
东方巴黎~Sunsiny30 分钟前
给定数字 [3, 30, 34, 5, 9] 拼接成的最大数字,使用java实现
java·开发语言
daiyang123...35 分钟前
Java 复习 【知识改变命运】第九章
java·开发语言·算法
Erosion20201 小时前
RMI原理及常见反序列化攻击手法
java·反序列化·java sec
AskHarries1 小时前
Spring Cloud Consul实现选举机制
java·后端·spring cloud·consul
山山而川粤1 小时前
大连环保公益管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·后端·学习·mysql
尘浮生1 小时前
Java项目实战II基于SpringBoot前后端分离的网吧管理系统(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
jakeswang2 小时前
spring循环依赖以及MyBatis-Plus的继承特性导致循环依赖自动解决失效
java·spring·mybatis
疯一样的码农2 小时前
使用命令行创建一个简单的 Maven Web 应用程序
java·maven
SlothLu2 小时前
Debezium-BinaryLogClient
java·mysql·kafka·binlog·多线程·debezium·数据迁移